原题链接:https://www.lydsy.com/JudgeOnline/problem.php?id=3517
翻硬币
Description
有一个n行n列的棋盘,每个格子上都有一个硬币,且n为偶数。每个硬币要么是正面朝上,要么是反面朝上。每次操作你可以选定一个格子(x,y),然后将第x行和第y列的所有硬币都翻面。求将所有硬币都变成同一个面最少需要的操作数。
Input
第一行包含一个正整数n。
接下来n行,每行包含一个长度为n的01字符串,表示棋盘上硬币的状态。
Output
仅包含一行,为最少需要的操作数。
Sample Input
4
0101
1000
0010
0101
Sample Output
2
HINT
【样例说明】
对(2,3)和(3,1)进行操作,最后全变成1。
【数据规模】
对于100%的数据,n ≤ 1,000。
题解
最暴力的思路当然是列 n2 n 2 个异或方程组,然后高斯消元去接一个 n2 n 2 个方程, n n 个未知数的方程组,大概是的。。。
我们不如拿个
2×2
2
×
2
的矩阵玩玩,设
a
a
是否翻转为取值
0
0
或,
a
a
的目标状态为,
b,c,d
b
,
c
,
d
以此类推:
那么我们可以列出四个方程:
貌似这些式子有点奇妙??
我们把 (1)(2)(3) ( 1 ) ( 2 ) ( 3 ) 式异或起来:
震惊,其他未知数都被消掉了!!!其他的式子似乎也是如此,好像我们只需要将那某个点所在的一行一列的数全部异或起来,就能知道这个点是否需要反转。
推广一下:因为 n n 是偶数,所以一定可以通过将个方程异或起来得到上面的那种形状。
事情变得简单起来。
代码
#include<bits/stdc++.h>
#define val (sq[i][j]-'0')
using namespace std;
const int M=1e3+5;
int n;
bool x[M],y[M];
char sq[M][M];
void in()
{
scanf("%d",&n);
for(int i=1;i<=n;++i)
{
scanf("%s",sq[i]+1);
for(int j=1;j<=n;++j)x[i]^=val,y[j]^=val;
}
}
void ac()
{
int ans=0;
for(int i=1;i<=n;++i)for(int j=1;j<=n;++j)ans+=x[i]^y[j]^val^1;
printf("%d",min(n*n-ans,ans));
}
int main()
{
in();ac();
return 0;
}