Bzoj3517 翻硬币题解 解异或方程组
3517: 翻硬币
Time Limit: 1 Sec Memory Limit: 128 MBSubmit: 281 Solved: 211
[Submit][Status][Discuss]
Description
有一个n行n列的棋盘,每个格子上都有一个硬币,且n为偶数。每个硬币要么是正面朝上,要么是反面朝上。每次操作你可以选定一个格子(x,y),然后将第x行和第y列的所有硬币都翻面。求将所有硬币都变成同一个面最少需要的操作数。
Input
第一行包含一个正整数n。
接下来n行,每行包含一个长度为n的01字符串,表示棋盘上硬币的状态。
Output
仅包含一行,为最少需要的操作数。
Sample Input
4
0101
1000
0010
0101
0101
1000
0010
0101
Sample Output
2
HINT
【样例说明】
对(2,3)和(3,1)进行操作,最后全变成1。
【数据规模】
对于100%的数据,n ≤ 1,000。
上来一看,第一反应,异或数学题,想了半天如何异或也没想出来,问呵呵酵母菌,他说他觉得是图论WTF?!图论有几个O(n)算法能在这道题用上的。
于是乎看了一眼题解:解异或方程组……
一个点最多翻一遍,这话不用再说了吧……
让我们先从都翻为0开始说起
我们设x[i][j]为第i,j个点是否要翻,a[i][j]为该点初始状态,则x[1][j]^x[2][j]^……^x[n][j]^x[i][1]^x[i][2]^x[i][m]^x[i][j]=a[i][j]。
我们把第i行和第j列所有的点按照上式列出方程组并合并, 由于n为偶数,则可以化为:
x[i][j]=a[1][j]^a[2][j]^……^a[n][j]^a[i][1]^a[i][2]^……^a[i][m]^a[i][j]。
那么我们只要对于每一行,每一列n^2预处理出他们的异或和再相加就好了。
至于都为1吗?由于n是偶数,我们只要把每一个点是否翻的状态取反就是答案。
1 #include <iostream> 2 #include <cstdlib> 3 #include <cstdio> 4 #include <cstring> 5 #include <queue> 6 #include <algorithm> 7 #include <cmath> 8 #include <map> 9 #define N 1005 10 using namespace std; 11 int n,a[N][N]; 12 char b[N]; 13 int sum[2][N]; 14 int main() 15 { 16 scanf("%d",&n); 17 for(int i=1;i<=n;i++) 18 { 19 scanf("%s",b+1); 20 for(int j=1;j<=n;j++) 21 { 22 a[i][j]=b[j]-'0'; 23 } 24 } 25 for(int i=1;i<=n;i++) 26 { 27 for(int j=1;j<=n;j++) 28 { 29 sum[0][i]^=a[i][j]; 30 sum[1][j]^=a[i][j]; 31 } 32 } 33 int ans=0; 34 for(int i=1;i<=n;i++) 35 { 36 for(int j=1;j<=n;j++) 37 { 38 int t=sum[0][i]^sum[1][j]; 39 t^=a[i][j]; 40 ans+=t; 41 } 42 } 43 ans=min(ans,n*n-ans); 44 printf("%d\n",ans); 45 return 0; 46 }