题目
思路
最少的分
O ( n m ) \mathcal O(nm) O(nm) 的动态规划走一波,大概能有 20 20 20 分。
更多的分
如果 n ≤ 5 n\le 5 n≤5 ,并且一个点的五列以内没有球洞,答案就是零。因为先后手都有将球打出界的方法。
全部的分
考虑优化 d p dp dp 。不难发现,让得分最大和最小的情况是类似的(将球洞的权值取相反数即可),这里只讨论让权值最小的情况。
沿用题面中 g g g 的定义。假设没有球洞的干扰,一定有 d p dp dp 转移式
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)=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,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+1,y+1)<min[g(x+2,y),g(x,y+2)] ,那么 g ( x , y ) g(x,y) g(x,y) 将会变成 min \min min 。
- 否则, g ( x , y ) g(x,y) g(x,y) 保持为 g ( x + 1 , y + 1 ) g(x+1,y+1) g(x+1,y+1) 。
如果我们将一个斜行看成一个单位,不难发现,大概就是这个意思:
偷盗来的图片,这里就不复制了,浪费网络资源。
用同一种颜色表示对应位。也就是说,对于每一个 左下-右上 对角线,看做一个序列,同种颜色的格子处于对应的序列中的相同位置。此时,我们研究 d p dp dp 值的变化,如果原本是一个凹陷 a > b < c a>b<c a>b<c ,那么 b ′ b' b′ 将会变成两边较低的那一个,使得其不再为低谷;如果不是凹陷,则 b ′ = b b'=b b′=b 。
于是我们放心地说,在没有球洞的干扰时, d p dp dp 值在推导一次后将会变得“稳定”。这里的稳定指的是不与球洞直接相邻。
有球洞呢?球洞导致一个单点的 d p dp dp 值发生改变,这一位和周围两个点对应的 左上-右下 对角线需要推两个,以后都靠“稳定性”来求。这个点的上方、左边的点的 d p dp dp 值也会发生变化,也需要推两个。
用 x x x 表示直接变化的点, y y y 表示附属变化的点(受球洞干扰), z z z 表示让 d p dp dp 值变的稳定的步骤,则大概是
( z 0 z z z z y z 0 z y x ) \begin{pmatrix} & & & z_0\\ & & z & z\\ & z & z & y\\ z_0 & z & y & x \end{pmatrix} ⎝⎜⎜⎛z0zzzzyz0zyx⎠⎟⎟⎞
z 0 z_0 z0 在我们的理论中是可能被影响到的,但实际上不会。若要探究为何,还需回归博弈本身,不难发现,先手可以保证球始终在起点周围两对角线内,即
( x y z y x y z z y x y z z y x y z y x ) \begin{pmatrix} x & y & z\\ y & x & y & z\\ z & y & x & y & z\\ & z & y & x & y\\ & & z & y & x \end{pmatrix} ⎝⎜⎜⎜⎜⎛xyzyxyzzyxyzzyxyzyx⎠⎟⎟⎟⎟⎞
加上也是可以的。
代码
/*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;
}