有N个相同的开关,每个开关都与某些开关有着联系,每当你打开或者关闭某个开关的时候,其他的与此开关相关联的开关也会相应地发生变化,即这些相联系的开关的状态如果原来为开就变为关,如果为关就变为开。
你的目标是经过若干次开关操作后使得最后N个开关达到一个特定的状态。
对于任意一个开关,最多只能进行一次开关操作。
你的任务是,计算有多少种可以达到指定状态的方法。(不计开关操作的顺序)
输入格式
输入第一行有一个数K,表示以下有K组测试数据。
每组测试数据的格式如下:
第一行 一个数N(0 < N < 29)。
第二行 N个0或者1的数,表示开始时N个开关状态。
第三行 N个0或者1的数,表示操作结束后N个开关的状态。
接下来 每行两个数I J,表示如果操作第 I 个开关,第J个开关的状态也会变化。
每组数据以 0 0 结束。
输出格式
如果有可行方法,输出总数,否则输出Oh,it’s impossible~!! 。
输入样例:
2
3
0 0 0
1 1 1
1 2
1 3
2 1
2 3
3 1
3 2
0 0
3
0 0 0
1 0 1
1 2
2 1
0 0
输出样例:
4
Oh,it’s impossible~!!
思路:
一开始一直没搞懂为啥可以高斯消元,原因是本题可以列异或方程,而且这个方程也符合初等行运算,只是运算方式变成了异或。
的意思是按下第j个数字是否影响第i个数字,故每一位只有0和1
a数组可以用状压的方式存储(或者bitset),不用开两维。
特别的
可以列出n个这样的方程:
我们要求的是方案数,
高斯消元过程中出现a[i] = 0,
也就是系数与最右边的数为0的情况时,意味着本行以下的变元都为自由元了,取0取1都无妨,答案就是2cnt,cnt代表包括当前行的剩余行数
如果出现a[i] = 1的情况,
意味着出现了0 = 1的情况,也就是系数为0,方程最右边为1。
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int a[1005];
int main()
{
int T;scanf("%d",&T);
while(T--)
{
int n;scanf("%d",&n);
for(int i = 1;i <= n;i++)
{
scanf("%d",&a[i]);
}
for(int i = 1;i <= n;i++)
{
int x;scanf("%d",&x);
a[i] ^= x;
a[i] |= 1 << i;
}
int x,y;
while(~scanf("%d%d",&x,&y) && x && y)
{
a[y] |= 1 << x;
}
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]);//最高位提前
}
if(a[i] == 0){ans = 1 << (n - i + 1);break;}//如果a[i] == 0,说明之后的元系数都为0,已经算出不来了成为了自由元
if(a[i] == 1){ans = 0;break;}//0 == 1,无解了。
for(int k = n;k >= 1;k--)
{
if(a[i] & (1 << k))
{
for(int j = 1;j <= n;j++)
{
if(i != j && a[j] & (1 << k))a[j] ^= a[i];//消掉其他方程的a[i]最高位(第k位)
}
break;
}
}
}
if(ans == 0)printf("Oh,it's impossible~!!\n");
else printf("%d\n",ans);
}
return 0;
}