POJ1830(开关问题 Gauss消元)

本文探讨了一个涉及多个相互关联开关的状态转换问题,通过构建数学模型,采用高斯消元法求解线性方程组,计算出使所有开关达到指定状态的不同方法的数量。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

problem

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

Input

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

Output

如果有可行方法,输出总数,否则输出“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~!!

Hint

第一组数据的说明:
一共以下四种方法:
操作开关1
操作开关2
操作开关3
操作开关1、2、3 (不记顺序)

思路

首先根据灯泡的起始状态和末尾状态可以确定出某个灯泡变换的次数%2的值 即最终效果是变还是不变

这样未知数[x1,x2,...,xn][x1,x2,...,xn] 表示初始状态

对于系数矩阵 其表示:行号表示的某个开关会被哪些灯影响,会影响的赋1,其余赋0 对角线赋1

a11a21a31a12a22a32a13a23a33[a11a12a13a21a22a23a31a32a33]

而经过乘法组合和右边的值就为初始末尾异或值

即求解

a11a21a31a12a22a32a13a23a33x1x2x3=[a11a12a13a21a22a23a31a32a33]∗[x1x2x3]=[初末异或]

ans为2自由变元的个数2自由变元的个数





为什么这样的可行呢?

因为在%2意义下行变换方程自由变元的个数是一样的 (和不%2相比)

所以不需考虑%2
因为2是质数?

代码示例

//高斯消元 时间复杂度O(n^3) 使用浮点数计算
#include<iostream>
#include<string.h>
#include<math.h>
using namespace std;
const double eps=1e-8;
const int maxn=101;

//也可以避免实数运算 使用辗转相减的方法 多一个log的时间复杂度
//特别适合于行列式求值 取模操作 (除法要求逆元)
int solve(double a[][maxn],bool l[],double ans[],const int &n,const int &m){
    //a为方程组对应的矩阵
    //l,ans存储解  l[]表示是否为自由变元 1表示不是 0表示是
    //n为未知数的个数 m为方程的个数
    //如果无解返回-1 否则返回自由变元数
    int res=0,r=0;//r为第几行 res为自由变元数
    for(int i=0;i<n;++i) l[i]=false;//开始都是自由变元

    for(int i=0;i<n;++i){//枚举列

        for(int j=r;j<m;++j)//枚举行
            if(fabs(a[j][i])>eps){
                //找到当前列下从第r行开始第一个不为零的元素并交换到第r行
                //如果一直为0 则下面会continue res++ 即自由变元+1
                //如果有不为0的 把它调上来(交换行)
                for(int k=i;k<=n;++k)//第j行和第r行交换 因为a[j][i]!=0
                    swap(a[j][k],a[r][k]);
                break;
            }

        //从a[r][i]这一个元素开始 往下的每个元素都是0了 所以这个元素xi是自由变元 i+1跳过即可
        if(fabs(a[r][i])<eps){
            ++res;
            continue;
        }

        for(int j=0;j<m;++j)//j是行数 从第一行(j=0)开始  让上三角更简洁 这样后面求ans就没有必要从下往上了
            if(j!=r && fabs(a[j][i])>eps){
                double tmp=a[j][i]/a[r][i];
                for(int k=i;k<=n;++k)
                    a[j][k]-=tmp*a[r][k];
            }
        l[i]=true,++r;
    }

    //检查是否无解
    for(int i=n-res;i<m;++i){
        if(fabs(a[i][n])>eps) return -1;
    }


    //下面求结果
    for(int i=0;i<n;++i)
        if(l[i])//不是自由变元
            for(int j=0;j<n;++j)
                if(fabs(a[j][i])>eps)
                    ans[i]=a[j][n]/a[j][i];
    return res;//返回自由变元数
}

int main()
{
    ios::sync_with_stdio(false);
    double a[maxn][maxn];
    bool l[maxn];
    double ans[maxn];
    int n,m,res;
    int t;
    cin>>t;
    while(t--)
    {
        cin>>n;
        m=n;
        int ta,tb;
        memset(a,0,sizeof(a));
        for(int i=0;i<n;++i) cin>>a[i][n],a[i][i]=1;
        for(int i=0;i<n;++i) cin>>ta,a[i][n]=int(a[i][n])^ta;
        while(cin>>ta>>tb&&(ta+tb))
        {
            a[tb-1][ta-1]=1;
        }
        res=solve(a,l,ans,n,m);
        if(res==-1) cout<<"Oh,it's impossible~!!"<<endl;
        else cout<<(1<<res)<<endl;
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值