开关问题(高斯消元,异或方程组)

POJ

有N个相同的开关,每个开关都与某些开关有着联系,每当你打开或者关闭某个开关的时候,其他的与此开关相关联的开关也会相应地发生变化,即这些相联系的开关的状态如果原来为开就变为关,如果为关就变为开.你的目标是经过若干次开关操作后使得最后N个开关达到一个特定的状态.对于任意一个开关,最多只能进行一次开关操作/算有多少种可以达到指定状态的方法?(不计开关操作的顺序)

分析:设(x_1=1(0))表示(没)开了这个开关,(a_{i,j}=1(0))表示(i,j)这两个开关有(无)联系,则(a_{i,i}=1).

(a_{1,1}x_1xora_{1,2}x_2xor...xora_{1,n}x_n=)初状态xor末状态(很好理解:只有同时满足 开关j影响i 且 j开了 这两个条件,才会对i的状态产生影响)

以此类推可以得到n个这样的方程,构成异或方程组.然后直接高斯消元即可.

我们还可以把一个方程状态压缩为一个int型整数.

#include<bits/stdc++.h>
#define LL long long
using namespace std;
inline LL read(){
    LL s=0,w=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-') w=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){s=s*10+ch-'0';ch=getchar();}
    return s*w;
}
int a[105];
int main(){
    int T=read();
    while(T--){
		int n=read();
		for(int i=1;i<=n;i++)a[i]=read();
//第i个开关的初状态
		for(int i=1;i<=n;i++){
	    	int j=read();//读入第i个开关的末状态
	    	a[i]^=j;//方程组的右边:初状态xor末状态
            a[i]|=1<<i;//状态压缩:a[i][i]=1
		}
		while(1){
	    	int x=read(),y=read();//x影响y
	    	if(x==0&&y==0)break;
	    	a[y]|=1<<x;//状态压缩:a[y][x]=1
		}
		int ans=1;
		for(int i=1;i<=n;i++){
	    	for(int j=i+1;j<=n;j++)
				if(a[j]>a[i])swap(a[i],a[j]);
//找到最大的一个a[i],即主元位数最高的a[i]
	    	if(a[i]==0){ans=1<<(n-i+1);break;}
//0=0,消元完毕,有i-1个主元,n-i+1个自由元
	    	if(a[i]==1){ans=0;break;}
//1=0,方程无解
//用a[i]去消其它方程
	    	for(int k=n;k;k--)
				if((a[i]>>k)&1){//从高位到低位,找为1的那一位
		    		for(int j=1;j<=n;j++)
						if(i!=j&&((a[j]>>k)&1))a[j]^=a[i];
//如果第j个方程的这一位也是1,则可以把它消去
		    	    break;
				}
		}
		if(ans==0)puts("Oh,it's impossible~!!");
		else printf("%d
",ans);
    }
    return 0;
}

原文地址:https://www.cnblogs.com/PPXppx/p/10604033.html