bzoj 1923
高斯消元解异或方程组裸题
首先介绍一下异或方程组:异或方程组是形如$x_1$^$x_2$^...^$x_n$=$1(或0)$($x_i\in{0,1}$)的一组方程,我们的目的是求出x_i的值
乍一看,这好像并不好做,最暴力的方法是$O(2^n)$枚举
但是有了高斯消元,我们就可以不这么暴力了,而是可以借助高斯消元的思想来解了
举个例子:
现在我们有一个方程组长这样:
$x_1$^$x_3$=$0$
$x_1$^$x_2$=$0$
$x_1$^$x_2$^$x_3$=$1$
(当然,给出的例子是很简单的了)
但是我们可以借助这种思想:我们首先观察第一个方程:我们希望只有这一个方程中含有$x_1$(这也是高斯消元的思想),这样在其他变量已知的情况下我们就可以求出$x_1$了
那么根据异或的性质,我们用第一个方程逐个与下面含有$x_1$的方程异或,就可以消去下面所有的$x_1$!
于是做出来的结果就长这样:
$x_1$^$x_3$=$0$
$x_2$^$x_3$=$0$
$x_2$=$1$
接下来就是第二个方程:我们要把下面的$x_2$消掉,于是我们的方法同上
最后结果长这样:
$x_1$^$x_3$=$0$
$x_2$^$x_3$=$0$
$x_3$=$1$
最后一个方程,发现已经结束了,那么到这里结束,我们向上回代即可
而回代的原理,也很简单:
$a$^$b$=$c$可以推出$a$=$c$^$b$(两边异或b即得证)
所以我们算出$x_3$后将所有有$x_3$的等式两侧异或$x_3$即可,于是方程就变成了这样:
$x_1$=$1$
$x_2$=$1$
$x_3$=$1$
是不已经解出来了?
这就是高斯消元解异或方程组的过程及原理
但是这里会出现一个问题:“自由元”问题
按道理,给出n个方程组应该是可以解出每个答案的,但是由于异或运算特殊的性质,有时n个方程是难以解出每个值的
为什么?
举个例子:
$x_1$^$x_2$=1
$x_2$^$x_3$=1
$x_1$^$x_3$=0
不难发现,这组方程本身解就不唯一(显然,$(1,0,1)$与$(0,1,0)$都是该方程组的解)
(如果去探索本质的原因:上面的异或方程在逻辑上等同于以下两个方程:$x_1==x_3$且$x_1!=x_2$,那么考虑取值的方式,不难发现解不是唯一的)
那么如果用正常的方式来解,最后会变成这种形式:
$x_1$^$x_2$=1
$x_2$^$x_3$=1
这也能看出来,因为丢失了一个方程啊!
这时我们就称$x_3$是一个自由元,也就是他的值既可以为0又可以为1,此时该异或方程组有很多组解!
对于自由元问题,目前仅有的办法就是dfs,枚举每个自由元的状态然后回代求解
但是对于这道题,我们没有必要这么做
由于对于解不唯一的情况,他只要求输出一句话,所以我们只需在解不唯一的情况下(也就是解了全部方程仍不能得到唯一解时)特判即可
接下来我们就可以研究如何输出最小方程数了
首先有个显而易见的事情:至少需要n个方程才能解出所有未知数
那我们就先拿出n个方程去解,看看是否存在自由元,如果存在的话就再加入一个,直到解出唯一解或用完所有方程为止
要记录所有自由元的位置
然后就结束了
#include <cstdio> #include <cmath> #include <cstring> #include <cstdlib> #include <algorithm> #include <queue> #include <stack> using namespace std; int n,m; int a[2005][1005]; char ch[2005]; bool vis[2005]; int Gauss() { int ret=0; for(int i=1;i<=n;i++) { int temp=i; while(!a[temp][i]&&temp<=n)temp++; if(temp==n+1){ret++;continue;} vis[i]=1; if(i!=temp)for(int j=i;j<=n+1;j++)swap(a[temp][j],a[i][j]); for(int j=i+1;j<=n;j++) { if(!vis[j]&&a[j][i])for(int k=i;k<=n+1;k++)a[j][k]^=a[i][k]; } } return ret; } inline int read() { int f=1,x=0;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} return x*f; } int main() { n=read(),m=read(); for(int i=1;i<=m;i++) { scanf("%s",ch+1); a[i][n+1]=read(); for(int j=1;j<=n;j++)a[i][j]=ch[j]-'0'; } int now=n; while(Gauss()) { for(int i=1;i<=n;i++) { if(!vis[i]) { now++; if(now>m) { printf("Cannot Determine\n"); return 0; } swap(a[now],a[i]); } } } for(int i=n;i>=1;i--)for(int j=i-1;j>=1;j--)if(a[j][i])a[j][n+1]^=a[i][n+1]; printf("%d\n",now); for(int i=1;i<=n;i++) { if(a[i][n+1])printf("?y7M#\n"); else printf("Earth\n"); } return 0; }