『开关问题 异或高消』

<更新提示>

<第一次更新>


<正文>

开关问题(POJ 1830)

Description

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

你的任务是,计算有多少种可以达到指定状态的方法。(不计开关操作的顺序)

Input Format

输入第一行有一个数K,表示以下有K组测试数据。 每组测试数据的格式如下: 第一行 一个数N(0 < N < 29) 第二行 N个0或者1的数,表示开始时N个开关状态。 第三行 N个0或者1的数,表示操作结束后N个开关的状态。 接下来 每行两个数I J,表示如果操作第 I 个开关,第J个开关的状态也会变化。每组数据以 0 0 结束。

Output Format

如果有可行方法,输出总数,否则输出“Oh,it's impossible~!!” 不包括引号

Sample Input

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  

Sample Output

4  
Oh,it's impossible~!!  

解析

\(x_i\)代表是否操作了第\(i\)个开关,按了为\(1\),不按为\(0\)\(f_{ij}\)代表开关\(i\)和开关\(j\)的联系情况,按下\(j\)会影响\(i\)\(f_{ij}=1\),反之\(f_{ij}=0\),对于任意的\(i\),令\(f_{ii}=1\)

如果将第\(i\)个开关最后是否变化记为\(p_i\)的话,就可以得到异或方程组:

\[\begin{cases} f_{11}x_{1}\ xor \ f_{12}x_{2}\ xor\ ... \ xor\ f_{1n}x_n=p_1 \\ f_{21}x_{1}\ xor \ f_{22}x_{2}\ xor\ ... \ xor\ f_{2n}x_n=p_2 \\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ ... \\ f_{n1}x_{1}\ xor \ f_{n2}x_{2}\ xor\ ... \ xor\ f_{nn}x_n=p_n \end{cases} \]

同样地,这个方程组也是可以高斯消元的,只需将加减操作改为异或即可。

对于本题,求的是方案数,那么我们分高斯消元的三种情况处理即可。当方程组恰好有解时,方案数为\(1\),当方程组无解时,方案数为\(0\),当方程组无穷解时,记录下自由元的个数\(p\),由于每个\(x\)可以取\(1\)\(0\),所以方案数为\(2^{p}\)

由于本题增广矩阵中所有元素非\(1\)\(0\),所以可以状态压缩一下,方便运算。

\(Code:\)

  #include <bits/stdc++.h>
using namespace std;
const int N=30;
int n,f[N],ans,t;
inline void input(void)
{
    scanf("%d",&n);
    for (int i=1;i<=n;i++)
        scanf("%d",&f[i]);
    int x,y;
    for (int i=1;i<=n;i++)
    {
        scanf("%d",&x);
        f[i] ^= x;
    }
    while ( scanf("%d%d",&x,&y) && x && y )
        f[y] |= 1 << x;
}
inline void init(void)
{
    for (int i=1;i<=n;i++)
        f[i] |= 1 << i;
}
inline void Gauss(void)
{
    ans = 1;
    for (int i=1;i<=n;i++)
    {
        for (int j=i+1;j<=n;j++)
            if ( f[j] > f[i] )
                swap(f[j],f[i]);
        if (!f[i]){ans = 1 << (n-i+1);break;}
        if (f[i]==1){ans = 0;break;}
        for (int j=n;j>=1;j--)
        {
            if (f[i] >> j & 1)
            {
                for (int k=1;k<=n;k++)
                    if ( i ^ k && ( f[k] >> j & 1 ) )
                        f[k] ^= f[i];
                break;
            }
        }
    }
}
int main(void)
{
    freopen("switch.in","r",stdin);
    freopen("switch.out","w",stdout);
    scanf("%d",&t);
    while (t--)
    {
        input();
        init();
        Gauss();
        if ( !ans )printf("Oh,it's impossible~!!\n");
        else printf("%d\n",ans);
    }
    return 0;
}

<后记>

posted @ 2019-04-16 14:31  Parsnip  阅读(281)  评论(0编辑  收藏  举报