1. 程式人生 > 實用技巧 >BZOJ-3517 翻硬幣(異或方程組)

BZOJ-3517 翻硬幣(異或方程組)

題目描述

  有一個 \(n\)\(n(n\leq 1000)\) 列的棋盤,每個格子上都有一個硬幣,且 \(n\) 為偶數。每個硬幣要麼是正面朝上,要麼是反面朝上。每次操作你可以選定一個格子 \((x,y)\),然後將第 \(x\) 行和第 \(y\) 列的所有硬幣都翻面。求將所有硬幣都變成同一個面最少需要的運算元。

分析

  一個硬幣被翻兩次和不翻是等價的,因此每個硬幣至多被翻一次,設 \(x_{ij}\) 為第 \(i\) 行第 \(j\) 列的硬幣是否被翻,\(A_{i,j}\) 為硬幣初始狀態,\(A_{i,j}=1\) 為正面朝上,\(A_{i,j}=0\) 為反面朝上。\(n\) 為偶數這個條件提示我們從異或角度考慮,則第 \(a\)

行第 \(b\) 列的硬幣的狀態由以下方程決定:

\[\left( \bigoplus_{j=1}^n x_{a,j} \right)\oplus \left (\bigoplus_{i=1}^n x_{i,b}\right)\oplus x_{a,b}\oplus A_{a,b}=0 \]

  根據異或的性質:\(a\oplus b=c\Longrightarrow c\oplus b=a\),把 \(A_{a,b}\) 移動到等號右邊,即:

\[\left( \bigoplus_{j=1}^n x_{a,j} \right)\oplus \left (\bigoplus_{i=1}^n x_{i,b}\right)\oplus x_{a,b}=A_{a,b} \]

  列出 \(n^2\) 個方程,有重複項的方程相互異或抵消掉,最後得到:

\[x_{a,b}=\left( \bigoplus_{j=1}^n A_{a,j} \right)\oplus \left (\bigoplus_{i=1}^n A_{i,b}\right)\oplus A_{a,b} \]

  預處理一下每行每列異或值的字首和,求 $n^2 $ 個數時間複雜度可以降到 \(O(n^2)\)

程式碼

#include<bits/stdc++.h>
using namespace std;
const int N=1010;
int A[N][N];
int sum1[N],sum2[N];
int main()
{
    int n;
    cin>>n;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            scanf("%1d",&A[i][j]);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            sum1[i]=sum1[i]^A[i][j];
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            sum2[j]=sum2[j]^A[i][j];
    int ans=0;
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=n;j++)
        {
            int temp=sum1[i]^sum2[j];
            temp=temp^A[i][j];
            ans=ans+temp;
        }
    }
    cout<<min(ans,n*n-ans)<<endl;
    return 0;
}