POJ 1830高斯消元

某些开关的动作可能影响另一些开关的状态,因此以开关为节点,如果存在这种关系就加入一条有向边(开始我想成对称的了,浪费了很多时间- -),这样就构成了一个图,可以用邻接矩阵表示(但是要转置一下,后面细说)。当某个开关按下时,其自身状态改变,受其影响的开关的状态也改变。
用两个N维向量表示初始状态和结束状态,两者逐个元素异或,就得到了开关状态的变化。
以第一个样例输入为例分析,3个开关,两两相连,初始状态000,最终状态111,将对角线的0全部换成1,得矩阵A=
这里写图片描述
将矩阵每一列想象为一个开关按下后产生的效果(1表示状态翻转,0表示不变),比如,第二列就表示按下第二个开关,则第二个开关的本身状态要改变(这就是把对角线0换成1的原因),受第二个开关影响的开关j状态也要改变,恰好对应邻接矩阵中A[j, 2]=1
把A写成分块矩阵的形式,每一列作为一个子矩阵,则有A=[a1, a2, a3],此处ai均为列向量,设第i个开关按下次数为xi,xi=0或1(开关按两下和没按是等效的,0/1就够了)
记初始状态b0=[0,0,0],最终状态b1=[1,1,1],则状态变化b=b0^b1=[1,1,1],这里b也是列向量。目标就是求x1a1 + x2a2 +x3a3 = b的解的个数(此处的加是模2加,也就是异或,下同)
这个方程可以写成
这里写图片描述
下面就是解这个线性方程组
对增广矩阵[A b]做初等行变换,化成阶梯形(高斯消元法),如果存在[0,0,…,0,1]的行,就是无解;如果存在r行[0,0,…,0,0],就意味着有r个自由变量,因为这里的变量只取0/1,所以有2r个解;如果不存在[0,0,…,0,*],即把最后一行去掉后不存在全0行,则A为满秩矩阵,则方程组有唯一解。

//
//  main.cpp
//  HDU-Fighting
//
//  Created by  绿色健康文艺小清新 on ...
//  Copyright 2016年  绿色健康文艺小清新. All rights reserved.
//

#include <cmath>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>

using namespace std;

const int mod = 2;

int a[30][31];

int gcd(int a, int b){
    int t;
    while(b){
        t = b;
        b = a % b;
        a = t;
    }
    return a;
}

int lcm(int a, int b){
    return a / gcd(a, b) * b;
}

int Gauss(int equ, int var){
    int k, col;
    for (k = 0, col = 0; k < equ && col < var; ++k, ++col){
        int max_r = k;
        for (int i = k + 1; i < equ; ++i){
            if (abs(a[i][col]) > abs(a[max_r][col])) max_r = i;
        }
        if (max_r != k){
            for (int i = k; i <= var; ++i)
                swap(a[k][i], a[max_r][i]);
        }
        if (a[k][col] == 0){
            --k;
            continue;
        }
        for (int i = k + 1; i < equ; ++i){
            if (a[i][col] != 0) {
                int LCM = lcm(abs(a[i][col]), abs(a[k][col]));
                int ta = LCM / abs(a[i][col]);
                int tb = LCM / abs(a[k][col]);
                if (a[i][col] * a[k][col] < 0) tb = -tb;
                for (int j = col; j <= var; ++j) {
                    a[i][j] = ((a[i][j] * ta - a[k][j] * tb) % mod + mod) % mod;
                }
            }
        }
    }
    for (int i = k; i < equ; ++i){
        if (a[i][col] != 0) return -1;
    }
    return var - k;
}

int s[30], e[30];
long long int b[30];

void build(int n)
{
    int x, y;
    while(scanf("%d%d", &x, &y), x || y)
        a[y - 1][x - 1] = 1;
    for(int i = 0; i < n; i++)
        a[i][n] = s[i] ^ e[i];
}

void init()
{
    b[0] = 1;
    for(int i = 1; i < 30; i++)
        b[i] = b[i - 1] * 2;
}

int main(void)
{
    int t, n;
    scanf("%d", &t);
    init();
    while(t--)
    {
        scanf("%d", &n);
        for(int i = 0; i < n; i++)
        for(int j = 0; j <= n; j++)
            a[i][j] = 0;
        for(int i = 0; i < n; i++)
        {
            scanf("%d", &s[i]);
            a[i][i] = 1;
        }
        for(int i = 0; i < n; i++)
            scanf("%d", &e[i]);
        build(n);
        int ans = Gauss(n, n);
        if(ans == -1)
            printf("Oh,it's impossible~!!\n");
        else
            printf("%lld\n", b[ans]);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值