BZOJ3517 翻硬幣 異或方程
題意:
給你一個
的01矩陣,每次你可以選擇一個
,作用是把
這一行和
這一列都進行01取反,問最少操作多少次可以使所有數字都全變成同一種。保證
是偶數,
。
題解:
我們先假設要全都變成0。我們發現對一個位置
有影響的操作只會是所有操作中行是
的和所有操作中列是
的操作。我們發現把同一個位置翻轉兩次就會變回原來的狀態,這個很像異或兩次1答案不變,於是我們記
為這個位置是否變成和初始狀態不同的一面,如果變的話
,否則
,我們設
為
這個位置在一開始是0還是1。我們可以對每個位置列出一個方程,一共列出
個異或方程,形式是
,最後要異或上自己的
,原因是在前面的式子裡被異或了兩遍消掉了,然後還要異或上自己的初始值,如果要變成1就是等式右邊等於1。稍微變化一下,等式兩邊同時異或
,變成
。
對於這 個方程,我們暴力去解的話似乎是 的,所以我們需要優化解方程的過程。我們考慮我們要求的其實是每一個位置的 的和,於是我們想對於表示 這個位置的式子,把其他含 的式子都消掉,因為是未知的,取而代之我們要的是含 的式子,因為 是已知的。於是我們想辦法消去那些其他的含 的變數,我們去一個一個的異或,對於一個 或者 ,每次去異或 或者是 那個位置的方程。這樣我們會發現其實我們會把整個矩陣全都異或一遍,由於 是個偶數,那麼如果我們異或了一個橫座標不是 並且縱座標不是 的 ,那麼它所在的那一整行或者一整列就全部會被異或到,於是會被異或 次抵消掉,最後會變成 。我們發現,對於這個式子,我們只需要預處理出每一行和每一列的異或和,再異或上當前點的初始值,就可以知道這個點要變成0時的操作次數,然後對於所有的點求個和就好了。這樣就是 的了。然而我們求出全變成0的答案之後並不用重新再求一遍全都變成1的答案,只需要有 全變成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;
}