1. 程式人生 > >BZOJ3517 翻硬幣 異或方程

BZOJ3517 翻硬幣 異或方程

題目連結

題意:
給你一個 n n n*n 的01矩陣,每次你可以選擇一個 ( x ,

y ) (x,y) ,作用是把 x x 這一行和 y y
這一列都進行01取反,問最少操作多少次可以使所有數字都全變成同一種。保證 n n 是偶數, n < = 1000
n<=1000

題解:
我們先假設要全都變成0。我們發現對一個位置 ( x , y ) (x,y) 有影響的操作只會是所有操作中行是 x x 的和所有操作中列是 y y 的操作。我們發現把同一個位置翻轉兩次就會變回原來的狀態,這個很像異或兩次1答案不變,於是我們記 x [ i ] [ j ] x[i][j] 為這個位置是否變成和初始狀態不同的一面,如果變的話 x [ i ] [ j ] = 1 x[i][j]=1 ,否則 x [ i ] [ j ] = 0 x[i][j]=0 ,我們設 a [ i ] [ j ] a[i][j] ( i , j ) (i,j) 這個位置在一開始是0還是1。我們可以對每個位置列出一個方程,一共列出 n 2 n^2 個異或方程,形式是 x [ i ] [ 1 ]   x o r   x [ i ] [ 2 ] . . .   x o r   x [ i ] [ n ]   x o r   x [ 1 ] [ j ]   x o r   x [ 2 ] [ j ] . . .   x o r   x [ n ] [ j ]   x o r   x [ i ] [ j ]   x o r   a [ i ] [ j ] = 0 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 ] x[i][j] ,原因是在前面的式子裡被異或了兩遍消掉了,然後還要異或上自己的初始值,如果要變成1就是等式右邊等於1。稍微變化一下,等式兩邊同時異或 a [ i ] [ j ] a[i][j] ,變成 x [ i ] [ 1 ]   x o r   x [ i ] [ 2 ] . . .   x o r   x [ i ] [ n ]   x o r   x [ 1 ] [ j ]   x o r   x [ 2 ] [ j ] . . .   x o r   x [ n ] [ j ]   x o r   x [ i ] [ j ] = 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]

對於這 n 2 n^2 個方程,我們暴力去解的話似乎是 n 6 n^6 的,所以我們需要優化解方程的過程。我們考慮我們要求的其實是每一個位置的 x [ i ] [ j ] x[i][j] 的和,於是我們想對於表示 ( i , j ) (i,j) 這個位置的式子,把其他含 x x 的式子都消掉,因為是未知的,取而代之我們要的是含 a a 的式子,因為 a a 是已知的。於是我們想辦法消去那些其他的含 x x 的變數,我們去一個一個的異或,對於一個 x [ u ] [ j ] x[u][j] 或者 x [ i ] [ v ] x[i][v] ,每次去異或 ( u , j ) (u,j) 或者是 ( i , v ) (i,v) 那個位置的方程。這樣我們會發現其實我們會把整個矩陣全都異或一遍,由於 n n 是個偶數,那麼如果我們異或了一個橫座標不是 i i 並且縱座標不是 j j x [ u ] [ v ] x[u][v] ,那麼它所在的那一整行或者一整列就全部會被異或到,於是會被異或 n n 次抵消掉,最後會變成 x [ i ] [ j ] = a [ i ] [ 1 ]   x o r   a [ i ] [ 2 ] . . .   x o r   a [ i ] [ n ]   x o r   a [ 1 ] [ j ]   x o r   a [ 2 ] [ j ] . . .   x o r   a [ n ] [ j ]   x o r   a [ i ] [ j ] 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 ( n 2 ) O(n^2) 的了。然而我們求出全變成0的答案之後並不用重新再求一遍全都變成1的答案,只需要有 n 2 n^2- 全變成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;
}