DestinHistoire

 

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;
}

posted on 2020-12-01 21:26  DestinHistoire  阅读(59)  评论(0编辑  收藏  举报

导航