HDU5833 异或方程组的初步学习

题目

题目链接

题解

选出来的数相乘要能组成一个完全平方数,则这个完全平方数进行素数分解以后,相同的素因子的个数是偶数个。

基于这个结论,我们对与每一个候选数 a[i] a [ i ] 进行质因数分解(最多有303个不同的质因子)。

然后针对每一个质因子 p p ,列一个包含所有n个数的异或方程,如果某个数具有奇数个 p p ,那么这个数前的系数就应该为1,否则应该为0。方程的右面始终为0,这是因为平方数的质因子个数为偶数。

我们可以列出多达303个异或方程组成的异或方程组,我们使用高斯消元算法来解这个异或方程组,假设解得自由元的个数为res个。
那么最终答案就是2res1个。(要减去什么都不取的情况)。

总结

当发现这是一道涉及某个元素取或者不取的,并与xor或者奇偶相关的题目时,我们要思考一下异或方程组。

代码

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <bitset>
#include <cstring>
using namespace std;
typedef long long ll;
const int maxn = 307;
const int maxm = 307;
const ll mod = 1e9+7;
ll a[maxn];
bitset<maxn> ba[maxm];
int T,n,cas;
int primes[maxm];
int pc = 0;
int not_prime[2000];
int getprimes(){
    for(int i = 2;i <= 2000;++i){
        if(!not_prime[i]){
            primes[pc++] = i;
            for(int j = 2*i;j <= 2000;j += i)
                not_prime[j] = 1;
        }
    }
}
bool nofree[maxn];
int xor_gauss(int n,int m){
    /* 用于给定异或方程组消元
     * 返回自由变元的个数 n-j
     */
    memset(nofree,0,sizeof(nofree));
    int j = 0;
    for(int i = 0;i < n;++i){
        int sp = j;
        while(sp < m){
            if(ba[sp][i]){
                nofree[i] = true;
                break;
            }
            sp++;
        }
        if(sp < m){
            swap(ba[j],ba[sp]);
            for(int k = j+1;k < m;++k){
                if(ba[k][i]) ba[k] ^= ba[j];
            }
            ++j;
        }
    }
    //无解判定
    for(int k = j;k < m;++k){
        if(ba[k].count() == 1 && ba[k][n]) return -1;
    }
    return n-j;
}
int main(){
    getprimes();
    cin>>T;
    while(T--){
        for(int i = 0;i < maxm;++i) ba[i].reset();
        cin>>n;
        for(int i = 0;i < n;++i){
            scanf("%lld",&a[i]);
        }
        for(int i = 0;i < pc;++i){
            for(int j = 0;j < n;++j){
                int cc = 0;
                ll tmp = a[j];
                while(tmp % primes[i] == 0){
                    cc ^= 1;
                    tmp /= primes[i];
                }
                ba[i][j] = cc;
            }
        }
        int res = xor_gauss(n,pc);
        ll ans = 1;
        for(int i = 0;i < res;++i) ans = ans * 2 % mod;
        printf("Case #%d:\n%lld\n",++cas,ans-1);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值