版权声明:本文为博主原创文章,可以转载但是必须声明版权。 https://blog.csdn.net/forever_shi/article/details/84653854
题目链接
题意:
给你一个
n∗n的01矩阵,每次你可以选择一个
(x,y),作用是把
x这一行和
y这一列都进行01取反,问最少操作多少次可以使所有数字都全变成同一种。保证
n是偶数,
n<=1000。
题解:
我们先假设要全都变成0。我们发现对一个位置
(x,y)有影响的操作只会是所有操作中行是
x的和所有操作中列是
y的操作。我们发现把同一个位置翻转两次就会变回原来的状态,这个很像异或两次1答案不变,于是我们记
x[i][j]为这个位置是否变成和初始状态不同的一面,如果变的话
x[i][j]=1,否则
x[i][j]=0,我们设
a[i][j]为
(i,j)这个位置在一开始是0还是1。我们可以对每个位置列出一个方程,一共列出
n2个异或方程,形式是
x[i][1] xor x[i][2]... xor x[i][n] xor x[1][j] xor x[2][j]... xor x[n][j] xor x[i][j] xor a[i][j]=0,最后要异或上自己的
x[i][j],原因是在前面的式子里被异或了两遍消掉了,然后还要异或上自己的初始值,如果要变成1就是等式右边等于1。稍微变化一下,等式两边同时异或
a[i][j],变成
x[i][1] xor x[i][2]... xor x[i][n] xor x[1][j] xor x[2][j]... xor x[n][j] xor x[i][j]=a[i][j]。
对于这
n2个方程,我们暴力去解的话似乎是
n6的,所以我们需要优化解方程的过程。我们考虑我们要求的其实是每一个位置的
x[i][j]的和,于是我们想对于表示
(i,j)这个位置的式子,把其他含
x的式子都消掉,因为是未知的,取而代之我们要的是含
a的式子,因为
a是已知的。于是我们想办法消去那些其他的含
x的变量,我们去一个一个的异或,对于一个
x[u][j]或者
x[i][v],每次去异或
(u,j)或者是
(i,v)那个位置的方程。这样我们会发现其实我们会把整个矩阵全都异或一遍,由于
n是个偶数,那么如果我们异或了一个横坐标不是
i并且纵坐标不是
j的
x[u][v],那么它所在的那一整行或者一整列就全部会被异或到,于是会被异或
n次抵消掉,最后会变成
x[i][j]=a[i][1] xor a[i][2]... xor a[i][n] xor a[1][j] xor a[2][j]... xor a[n][j] xor a[i][j]。我们发现,对于这个式子,我们只需要预处理出每一行和每一列的异或和,再异或上当前点的初始值,就可以知道这个点要变成0时的操作次数,然后对于所有的点求个和就好了。这样就是
O(n2)的了。然而我们求出全变成0的答案之后并不用重新再求一遍全都变成1的答案,只需要有
n2−全变成0的答案就可以了。
代码:
#include <bits/stdc++.h>
using namespace std;
int n,x[1010],y[1010],ans;
char s[1010][1010];
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;++i)
scanf("%s",s[i]+1);
for(int i=1;i<=n;++i)
{
for(int j=1;j<=n;++j)
{
if(s[i][j]=='1')
{
x[i]^=1;
y[j]^=1;
}
}
}
for(int i=1;i<=n;++i)
{
for(int j=1;j<=n;++j)
ans+=x[i]^y[j]^(s[i][j]-'0');
}
printf("%d\n",min(ans,n*n-ans));
return 0;
}