3517: 翻硬币
Time Limit: 1 Sec Memory Limit: 128 MB
Submit: 289 Solved: 219
[Submit][Status][Discuss]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。
Source
题解
第一眼看起来怕是个高斯消元解异或方程组...然而这样会搞出 $n^2$ 个方程, 消元过程又需要 $O(n^3)$ 的时间复杂度, 总时间复杂度会达到 $O(n^6)$ , 在 $nleq 1000$ 的数据下不超时才是见鬼...
仔细观察方程特征:
如果我们设格子 $(a,b)$ 翻还是不翻为 $x_{a,b}$ , 初始状态设为 $d_{a,b}$ , 则可以得到形如下的方程:
[left( igoplus_{j=1}^n x_{a,j} ight)oplus left (igoplus_{i=1}^n x_{i,b} ight)oplus d_{a,b}oplus x_{a,b}=0]
然后进行变形, 将 $d_{a,b}$ 移到左端, 得到:
[left( igoplus_{j=1}^n x_{a,j} ight)oplus left (igoplus_{i=1}^n x_{i,b} ight)oplus x_{a,b}=d_{a,b}]
然后我们发现这 $n^2$ 个方程中有大量的重复项, 再加上异或运算的特殊性, 我们将有重复项的相关方程互相异或抵消掉相同项, 最后剩下的情况大概是形如这个样子的:
[x_{a,b}=left( igoplus_{j=1}^n d_{a,j} ight)oplus left (igoplus_{i=1}^n d_{i,b} ight)oplus d_{a,b}]
这样可以优化到单次求值 $O(n)$ , 对于 $n^2$ 个未知数总时间复杂度为 $O(n^3)$ (然而依然跑不过)
但是我们可以发现都是某行某列的异或和, 读入时顺便求一下异或和存到某个数组就可以了.
最终总时间复杂度 $O(n^2)$
参考代码
1 #include <cstdio> 2 #include <cstring> 3 #include <cstdlib> 4 #include <iostream> 5 #include <algorithm> 6 7 const int MAXN=1010; 8 9 int n; 10 char buf[MAXN]; 11 int xorSumx[MAXN]; 12 int xorSumy[MAXN]; 13 int data[MAXN][MAXN]; 14 15 int main(){ 16 scanf("%d",&n); 17 for(int i=0;i<n;i++){ 18 scanf("%s",buf); 19 for(int j=0;j<n;j++){ 20 data[i][j]=buf[j]-'0'; 21 // printf("%d ",data[i][j]); 22 xorSumx[i]^=data[i][j]; 23 xorSumy[j]^=data[i][j]; 24 // printf("%d %d ",xorSumx[i],xorSumy[j]); 25 } 26 } 27 int ans=0; 28 for(int i=0;i<n;i++){ 29 for(int j=0;j<n;j++){ 30 ans+=data[i][j]^xorSumx[i]^xorSumy[j]; 31 } 32 } 33 printf("%d ",std::min(n*n-ans,ans)); 34 return 0; 35 }