传送门:CF
[前题提要]:感觉这道题很有意思,故写篇题解记录一下
直接上图,显然如果我们想让路径和为0的话,走过的格子数应该是偶数个,那么就需要 n + m − 1 n+m-1 n+m−1为偶数,此处可以先特判一下.
接下来就是这种向下向右跑图的经典 t r i c k trick trick了,对于这种题目,有一种很经典的枚举路径的方法(在CF中经常出现),也就是将一个四格方块翻下去,或者将其翻上去.类似于下图(仅 举例翻下去的情况,翻上去同理):
不难证明通过上图的翻转方式可以将任意一条路径一步一步转化为另一种路径,证明此处略.
不失一般的,我们可以将上述的方法用到此题中去,我们会发现我们每使用上述的一次翻转操作,只会改变两个格子的值,也就是只会发生 + 2 , − 2 , 0 +2,-2,0 +2,−2,0这几种情况.也就是说,每次进行一次上述操作,竟然是不会改变奇偶性的.进一步也就是说所有路径和的奇偶性是相同的!.并且此时我们的格子数是偶数个,也就是说 1 , − 1 1,-1 1,−1的个数只能是偶+偶,或者奇+奇,无论是那种情况,我们的路径和都只能是偶数!
哇,这个性质只能说是真的妙!不愧是CF题
此时我们想要路径和变为0,显然0是偶数.所以我们只要找到路径和的最大值和最小值,使其包括0即可.找最大值和最小值可以通过经典的dp轻松完成.
下面是具体的代码部分:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define root 1,n,1
#define ls (rt<<1)
#define rs (rt<<1|1)
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
inline ll read() {
ll x=0,w=1;char ch=getchar();
for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') w=-1;
for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
return x*w;
}
inline void print(__int128 x){
if(x<0) {putchar('-');x=-x;}
if(x>9) print(x/10);
putchar(x%10+'0');
}
#define maxn 1000000
const double eps=1e-8;
#define int_INF 0x3f3f3f3f
#define ll_INF 0x3f3f3f3f3f3f3f3f
int a[1010][1010];int dp1[1010][1010],dp2[1010][1010];
int main() {
int T=read();
while(T--) {
int n=read();int m=read();
for(int i=1;i<=n;i++) {
for(int j=1;j<=m;j++) {
a[i][j]=read();
}
}
if((n+m-1)&1) {
cout<<"NO"<<endl;
continue;
}
for(int i=1;i<=n;i++) {
for(int j=1;j<=m;j++) {
dp1[i][j]=-int_INF;
dp2[i][j]=int_INF;
}
}
for(int i=1;i<=n;i++) {
dp1[i][1]=dp1[i-1][1]+a[i][1];
dp2[i][1]=dp2[i-1][1]+a[i][1];
}
for(int i=1;i<=m;i++) {
dp1[1][i]=dp1[1][i-1]+a[1][i];
dp2[1][i]=dp2[1][i-1]+a[1][i];
}
for(int i=2;i<=n;i++) {
for(int j=2;j<=m;j++) {
dp1[i][j]=max(dp1[i-1][j],dp1[i][j-1])+a[i][j];
dp2[i][j]=min(dp2[i-1][j],dp2[i][j-1])+a[i][j];
}
}
if(dp1[n][m]>=0&&dp2[n][m]<=0) {
cout<<"YES"<<endl;
}
else {
cout<<"NO"<<endl;
}
}
return 0;
}