高斯方程解01异或方程组(HDU-5833 zhu and 772002)

推荐两个博客:
https://www.jianshu.com/p/888f2c2b31bc
https://blog.csdn.net/weixin_43871207/article/details/108395566

我基本上就是看了上面两个博客,将代码的解析完善的,第一个博客理论讲的好,第二个博客代码写的好,每一句话都是精髓,如果不懂的话,一定反复阅读。

#include <cstdio>
#include <cstring>
#include <string>
#include <cmath>
#include <iostream>
#include <algorithm>
#include <vector>
#include <stack>
#include <sstream>
#include <map>
#include <set>
#include <queue>
#include <stdlib.h>
typedef long long ll;
using namespace std;
typedef long long ll;
const ll inf = 0x3f3f3f3f3f3f3f3f;
const ll mod = 1e9 + 7;
const ll N = 2005; //要找出2000以内的素数(一共303个)
ll a[2005][2005];//增广矩阵
ll x[2005];  //如果有唯一解,则计算解集,存到x数组中
ll freeX[N]; //标记是否为自由变元



ll prime[2005]; //用prime数组储存素数
bool vis[2005]; //vis数组,如果=1,则不是素数,如果!=1,则是素数,相当于一个标记函数
ll tot;         //tot为素数的个数

void init() //初始化 -》 将素数找出来,存入prime数组里面
{
    memset(vis, 0, sizeof(vis));
    vis[1] = vis[0] = 1; //1不是素数,0是素数
    tot = 0;
    for(ll i = 2; i < N; ++i)
    {
        if(vis[i]==0)
        {
            prime[tot++] = i;
            for(ll j = 2*i; j < N; j += i)
            {
                vis[j] = 1;
            }
        }
    }
}

void solve(ll id, ll n) //id是第几位数(从0开始),n是第几位数的数值,解决第几个数前面是1还是0的问题
{
    for(ll i = 0; i < tot; ++i) //tot为素数的个数
    {
        while(n % prime[i] == 0) //如果n是某一个素数的倍数
        {
            n /= prime[i];
            a[i][id] ^= 1;  //a[i][j] i为第几个素数,j为第几个样例 ,a[i][j]为第j个数含有多少个第i个素数,如果含有偶数个素数,则结果为0,否则为1
            //所以求解异或方程组自由变元的个数cnt,每个自由变元可取的值为0或1,所以答案为这些自由变元的组合,即2^cnt-1
        }
    }
}

ll Gauss(ll equ,ll var) //返回自由变元个数,equ为行数(素数的个数),var为列数(输入元素的个数,也就是n)
{
    //初始化:将解都设为0,标记都不是自由变元
    for(ll i=0;i<=var;i++)
    {
        x[i]=0;
        freeX[i]=0;
    }

    /*转换为阶梯阵*/
    ll col=0; //当前处理的列
    ll row;   //当前处理的行
    ll num=0; //自由变元的序号
    for(row=0; row<equ && col<var ; row++,col++) //枚举当前处理的行
    {
        ll maxRow = row; //当前列绝对值最大的行

        for(ll i=row+1;i<equ;i++) //寻找当前列绝对值最大的行
        {
            if(abs(a[i][col])>abs(a[maxRow][col])) maxRow=i;
        }

        if(maxRow!=row) //与第row行交换
        {
            for(ll j=row;j<var+1;j++)
            {
                swap(a[row][j],a[maxRow][j]);
            }
        }

        if(a[row][col]==0) //即当前这一列全是0,处理当前行的下一列
        {
            freeX[num++]=col;//记录自由变元
            row--;//在for循环里面row要++,因为处理当前行的下一列,行数不用变,只要变列数就可以了,所以row要--,抵消掉for循环里面的++
            continue;
        }

        for(ll i=row+1;i<equ;i++) //从该行的下一行一直到最后一行
        {
            if(a[i][col]!=0)
            {
                for(ll j=col;j<var;j++) //对于下面出现该列中有1的行,需要把1消掉
                {
                    a[i][j]^=a[row][j];
                }
            }
        }
    }

    /*求解*/

    //无解:化简的增广阵中存在(0,0,...,a)这样的行,且a!=0
    //因为n<303,即列数<行数,所以最后row的位置肯定是最后一列,但是不到最后一行,根据上面的for循环,从row行开始,一直到最后一行,前col-1列每个都是0,但是最后一列(col列)不一定都是0,如果有不是0的(就是1),那么无解。
    for(ll i=row;i<equ;i++) //从第row行开始,一直到最后一行,如果最后一列的元素!=0,则无解,直接返回-1
    {
        if(a[i][col]!=0)
        {
            return -1;
        }
    }

    //无穷解: 在var*(var+1)的增广阵中出现(0,0,...,0)这样的行(即n*(n+1)中出现某一行全是0这样的行)
    ll temp=var-row; //因为如果有自由变元,则只有列变化,行数是不变化的,所以最后的自由变元有n-row个
    if(row<var) //返回自由变元数
    {
         return temp;
    }


    //唯一解: 在var*(var+1)的增广阵中形成严格的上三角阵
    for(ll i=var-1;i>=0;i--) //计算解集,这道题可以不用计算解集,可以不用写
    {
        x[i]=a[i][var];
        for(ll j=i+1;j<var;j++)
        {
            x[i]^=(a[i][j]&&x[j]);
        }
    }
    //如果形成唯一解,最后返回的结果就是0,2^0-1=0
    return 0;
}

ll enumFreeX(ll freeNum,ll var) //枚举自由元,统计有解情况下1最少的个数 ,这道题这里也可以不用写
{
    ll sta=(1<<(freeNum));//自由元的状态总数
    ll res=inf;
    for(ll i=0;i<sta;++i){//枚举状态
        ll cnt=0;
        for(ll j=0;j<freeNum;j++){//枚举自由元
            if(i&(1<<j)){
                cnt++;
                x[freeX[j]]=1;
            }else
                x[freeX[j]]=0;
        }
        for(ll k=var-freeNum-1;k>=0;k--){//没有自由元的最下面一行
            ll index=0;
            for(index=k;k<var;index++){//在当前行找到第一个非0自由元
                if(a[k][index])
                    break;
            }
            x[index]=a[k][var];
            for(ll j=index+1;j<var;++j){//向后依次计算出结果
                if(a[k][j])
                    x[index]^=x[j];
            }
            cnt+=x[index];//若结果为1,则进行统计
        }
        res=min(res,cnt);
    }
    return res;
}

ll qpow(ll a, ll b) //快速幂
{
    ll ans = 1;
    while(b) {
        if(b & 1)
            ans = ans * a % mod;
        a = a * a % mod;
        b >>= 1;
    }
    return ans % mod;
}

int main()
{
    init(); //初始化,将素数放入prime数组里面,从prime[0]开始存
    ll t, n; //给的数不超过300(n<=300)
    ll tmp;
    cin >> t; //一共几组样例
    for (int I=1;I<=t;I++)
    {
        cin >> n; //给出几个数
        memset(a, 0, sizeof(a));   //a是增广矩阵
        for(ll i = 0; i < n; ++i)
        {
            cin >> tmp;
            solve(i, tmp);
        }
        ll freeNum = Gauss(tot,n); //用高斯消元求解异或方程组(解出自由元个数) (tot为素数的个数,n为一共输入多少个数)
        printf("Case #%d:\n%lld\n",  I , (qpow(2, freeNum) - 1 + mod) % mod); //最后的结果为2^cnt(自由变元个数)-1
    }
    return 0;
}
全部评论

相关推荐

头像
05-12 09:14
点赞 评论 收藏
转发
点赞 收藏 评论
分享
牛客网
牛客企业服务