題目
思路
最少的分
的動態規劃走一波,大概能有 分。
更多的分
如果 ,並且一個點的五列以內沒有球洞,答案就是零。因爲先後手都有將球打出界的方法。
全部的分
考慮優化 。不難發現,讓得分最大和最小的情況是類似的(將球洞的權值取相反數即可),這裏只討論讓權值最小的情況。
沿用題面中 的定義。假設沒有球洞的干擾,一定有 轉移式
其實就是把後手決策的這一步給壓縮了
然後稍微化簡一下得到
這個式子有啥意思捏?分類討論唄。
- 如果 ,那麼 將會變成 。
- 否則, 保持爲 。
如果我們將一個斜行看成一個單位,不難發現,大概就是這個意思:
偷盜來的圖片,這裏就不復制了,浪費網絡資源。
用同一種顏色表示對應位。也就是說,對於每一個 左下-右上 對角線,看做一個序列,同種顏色的格子處於對應的序列中的相同位置。此時,我們研究 值的變化,如果原本是一個凹陷 ,那麼 將會變成兩邊較低的那一個,使得其不再爲低谷;如果不是凹陷,則 。
於是我們放心地說,在沒有球洞的干擾時, 值在推導一次後將會變得“穩定”。這裏的穩定指的是不與球洞直接相鄰。
有球洞呢?球洞導致一個單點的 值發生改變,這一位和周圍兩個點對應的 左上-右下 對角線需要推兩個,以後都靠“穩定性”來求。這個點的上方、左邊的點的 值也會發生變化,也需要推兩個。
用 表示直接變化的點, 表示附屬變化的點(受球洞干擾), 表示讓 值變的穩定的步驟,則大概是
在我們的理論中是可能被影響到的,但實際上不會。若要探究爲何,還需迴歸博弈本身,不難發現,先手可以保證球始終在起點周圍兩對角線內,即
加上也是可以的。
代碼
/*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;
}