题目大意
有一个n*m的格子,每个格子上有一个硬币,用0表示正面朝上,1表示反面朝上。
一次操作你可以将一行或一列的硬币都反转,问你是否能够进行一系列的操作之后使得所有的硬币都朝上。
分析
这道题和黑白棋那道类似,黑白棋那道有个启发式信息是一个点不会反转超过一次,这道题也是同样的道理,一行或一列不会反转超过一次。
然后我们观察一种可能的反转情况:
我们可以发现这样的信息:反转的情况类似于“井”状,并且出于交叉出的点一定是0,因为只有0点在反转两次后才会依然朝上。
继续观察我们发现了一条有用的信息:
一种可行的反转操作集合的补集也一定是可行的,如下图:
这样,我们不妨将第一行反转,对于第一行中为0的点我们将这些点所在的列也反转,做了这样一个初始操作之后我们继续看后面的行,后面不可能在进行列反转操作了,因为这会破坏第一行的状态,所以我们只需要去统计后面每一行是否都是相同的数就行了。
还有需要注意的一点就是题目中给的图范围是n*m< 106 但并没有给n和m的范围,所以我们需要用一维数组去模拟二维数组,mat[x][y]用a[i]表示,其中i=(x-1)*m+y
代码
#include<cstdio>
#include<iostream>
#include<cmath>
#include<cstring>
#include<cstdlib>
#include<queue>
#include<map>
#include<algorithm>
#include<set>
#include<stack>
using namespace std;
const long long int INF=0x7fffffffffffffff;
const int MAXN= 1000005;
int a[MAXN];
int T;
int n,m;
bool x_change[MAXN];//x_change[i]为1表示第i行反转,0表示不反转
bool y_change[MAXN];
void Init()
{
memset(a,0,sizeof(a));
memset(x_change,0,sizeof(x_change));
memset(y_change,0,sizeof(y_change));
}
bool Work()
{
x_change[1]=1;
for(int j=1;j<=m;j++)
{
if(a[j]==0)y_change[j]=1;
}
int cnt;//
for(int i=1;i<=n;i++)//n行m列
{
cnt=0;//表示第i行有多少0
for(int j=1;j<=m;j++)
{
int loc=(i-1)*m+j;
if(a[loc]==0 && y_change[j]==0)cnt++;
if(a[loc]==1 && y_change[j]==1)cnt++;
}
if(cnt>0 && cnt<m)return 0;
}
return 1;
}
int main()
{
scanf("%d",&T);
while(T--)
{
Init();
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
scanf("%d",&a[(i-1)*m+j]);
}
}
if(Work()==1)printf("YES\n");
else printf("NO\n");
}
return 0;
}