[ROI 2019 Day2]機器人高爾夫球賽

題目

傳送門 to LOJ

思路

最少的分

O(nm)\mathcal O(nm) 的動態規劃走一波,大概能有 2020 分。

更多的分

如果 n5n\le 5 ,並且一個點的五列以內沒有球洞,答案就是零。因爲先後手都有將球打出界的方法。

全部的分

考慮優化 dpdp 。不難發現,讓得分最大和最小的情況是類似的(將球洞的權值取相反數即可),這裏只討論讓權值最小的情況。

沿用題面中 gg 的定義。假設沒有球洞的干擾,一定有 dpdp 轉移式

g(x,y)=min{max[g(x+2,y),g(x+1,y+1)],max[g(x+1,y+1),g(x,y+2)]}g(x,y)=\min\{\max[g(x+2,y),g(x+1,y+1)],\max[g(x+1,y+1),g(x,y+2)]\}

其實就是把後手決策的這一步給壓縮了

然後稍微化簡一下得到

g(x,y)=max{g(x+1,y+1),min[g(x+2,y),g(x,y+2)]}g(x,y)=\max\{g(x+1,y+1),\min[g(x+2,y),g(x,y+2)]\}

這個式子有啥意思捏?分類討論唄。

  • 如果 g(x+1,y+1)<min[g(x+2,y),g(x,y+2)]g(x+1,y+1)<\min[g(x+2,y),g(x,y+2)] ,那麼 g(x,y)g(x,y) 將會變成 min\min
  • 否則,g(x,y)g(x,y) 保持爲 g(x+1,y+1)g(x+1,y+1)

如果我們將一個斜行看成一個單位,不難發現,大概就是這個意思:

偷盜來的圖片,這裏就不復制了,浪費網絡資源。

用同一種顏色表示對應位。也就是說,對於每一個 左下-右上 對角線,看做一個序列,同種顏色的格子處於對應的序列中的相同位置。此時,我們研究 dpdp 值的變化,如果原本是一個凹陷 a>b<ca>b<c ,那麼 bb' 將會變成兩邊較低的那一個,使得其不再爲低谷;如果不是凹陷,則 b=bb'=b

於是我們放心地說,在沒有球洞的干擾時, dpdp 值在推導一次後將會變得“穩定”。這裏的穩定指的是不與球洞直接相鄰。

有球洞呢?球洞導致一個單點的 dpdp 值發生改變,這一位和周圍兩個點對應的 左上-右下 對角線需要推兩個,以後都靠“穩定性”來求。這個點的上方、左邊的點的 dpdp 值也會發生變化,也需要推兩個。

xx 表示直接變化的點,yy 表示附屬變化的點(受球洞干擾),zz 表示讓 dpdp 值變的穩定的步驟,則大概是

(z0zzzzyz0zyx)\begin{pmatrix} & & & z_0\\ & & z & z\\ & z & z & y\\ z_0 & z & y & x \end{pmatrix}

z0z_0 在我們的理論中是可能被影響到的,但實際上不會。若要探究爲何,還需迴歸博弈本身,不難發現,先手可以保證球始終在起點周圍兩對角線內,即

(xyzyxyzzyxyzzyxyzyx)\begin{pmatrix} x & y & z\\ y & x & y & z\\ z & y & x & y & z\\ & z & y & x & y\\ & & z & y & x \end{pmatrix}

加上也是可以的。

代碼

/*Lucky_Glass*/
#include<map>
#include<vector>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

#define fir first
#define sec second
const int N=1e5+10,MOD=998244353;
typedef pair<int,int> pii;

inline int Add(const int &A,const int &B){return A+B>=MOD? A+B-MOD:A+B;}
inline int Mul(const int &A,const int &B){return 1ll*A*B%MOD;}

map<int,int> las;
map<pii,int> key;

int rol,col,n;
vector<pii> sp;
pii f[N<<4]; //min(first),max(second)

inline int Ri(){
    register int a=0,b=1,c=getchar();
    while(c<'0' || '9'<c) b=c=='-'? -1:b,c=getchar();
    while('0'<=c && c<='9') a=(a<<1)+(a<<3)+c-'0',c=getchar();
    return a*b;
}
inline bool cmp(pii A,pii B){return A.fir+A.sec==B.fir+B.sec? A.fir<B.fir:A.fir+A.sec>B.fir+B.sec;}
inline pii Calc(int x,int y){
    if(key.count(make_pair(x,y))) return make_pair(key[make_pair(x,y)],key[make_pair(x,y)]);
    pii key1(0,0),key2(0,0);
    if(las.count(x-y+1)) key1=f[las[x-y+1]];
    if(las.count(x-y-1)) key2=f[las[x-y-1]];
    return make_pair(min(key1.sec,key2.sec),max(key1.fir,key2.fir));
}
inline int Model(const int &x){return (x%MOD+MOD)%MOD;}
int main(){
    rol=Ri(),col=Ri(),n=Ri();
    for(int i=1;i<=n;i++){
        int x=Ri(),y=Ri(),val=Ri();
        key[make_pair(x,y)]=val;
        //枚舉4*4的三角,標記這些位置要暴力計算
        for(int p=0;p<4&&x-p>0;p++)
            for(int q=0;q<4-p&&y-q>0;q++)
                sp.push_back(make_pair(x-p,y-q));
    }
    //把要暴力計算的位置排序、去重
    sort(sp.begin(),sp.end(),cmp);
    sp.erase(unique(sp.begin(),sp.end()),sp.end());
    long long ans=0;
    // 從右下處理到左上,每次考慮一條對角線
    for(int o=0,lo=sp.size();o<lo;o++){
        int tag=sp[o].fir+sp[o].sec,beg=o;
        while(o+1<lo && sp[o+1].fir+sp[o+1].sec==tag) o++;
        //找到在同一條左下-右上對角線上的暴力計算的點
        for(int i=beg;i<=o;i++){
            int x=sp[i].fir,y=sp[i].sec;
            //統計當前左上-右下對角線原來的DP值出現的次數並計算貢獻
            if(las.count(x-y)) ans=Add(ans,Mul(Model(f[las[x-y]].fir),sp[las[x-y]].fir-x));
            // 右邊乘的是出現的長度
            f[i]=Calc(x,y); // 計算某個點的 DP 值
            las[x-y]=i; //記錄當前左上-右下對角線的 DP 值
        }
    }
    //還有一些 DP 值的貢獻
    for(map<int,int>::iterator it=las.begin();it!=las.end();it++)
        ans=Add(ans,Mul(Model(f[it->sec].fir),min(sp[it->sec].fir,sp[it->sec].sec)));
    printf("%d\n",ans);
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章