2016ccpc网络赛 HDU - 5833 Zhu and 772002(高斯消元求解异或方程组)

Zhu and 772002 are both good at math. One day, Zhu wants to test the ability of 772002, so he asks 772002 to solve a math problem. 

But 772002 has a appointment with his girl friend. So 772002 gives this problem to you. 

There are nn numbers a1,a2,...,ana1,a2,...,an. The value of the prime factors of each number does not exceed 20002000, you can choose at least one number and multiply them, then you can get a number bb. 

How many different ways of choices can make bb is a perfect square number. The answer maybe too large, so you should output the answer modulo by 10000000071000000007.

Input

First line is a positive integer TT , represents there are TT test cases. 

For each test case: 

First line includes a number n(1≤n≤300)n(1≤n≤300),next line there are nn numbers a1,a2,...,an,(1≤ai≤1018)a1,a2,...,an,(1≤ai≤1018).

Output

For the i-th test case , first output Case #i: in a single line. 

Then output the answer of i-th test case modulo by 10000000071000000007.

Sample Input

2
3
3 3 4
3
2 2 2

Sample Output

Case #1:
3
Case #2:
3

题意:

给出 n 个数的序列,问从序列中选出至少一个数相乘可以得到一个完全平方数的方法数。

思路:

一个完全平方数的所有素因子的幂次均为偶数,对1~2000内的每个素数建立一个方程 (ai1 * x1) ^ (ai2 * x2) ^ .... ^ (ain * xn) = bi

为了满足所有素因子的个数都是偶数,bi = 0,aij为第 j 个数含有多少个第 i 个素数,偶数为0,奇数为1,异或之后为0说明该素因子的个数是偶数。所以求解异或方程组自由变元的个数 cnt,每个自由变元可取的值为0或1,所以答案为这些自由变元的组合,即 2 ^ cnt - 1。

本题打2000以内素数表,然后求质因数个数。。。的确是第一次接触这种题吧,一点思路都没有

高斯消元模板:https://blog.csdn.net/u011815404/article/details/88890702

#include <iostream>
#include <cstdio>
#include <cmath>
#include <vector>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
typedef long long ll;
const ll inf = 0x3f3f3f3f3f3f3f3f;
const ll mod = 1e9 + 7;
const ll N = 2005;
ll a[N][N];//增广矩阵
ll x[N];//解集
ll freeX[N];//标记是否为自由变元
ll prime[2005], tot;
bool vis[2005];

void init() {
    memset(vis, 0, sizeof(vis));
    vis[1] = vis[0] = 1;
    tot = 0;
    for(ll i = 2; i < N; ++i) {
        if(!vis[i]) {
            prime[tot++] = i;
            for(ll j = i + i; j < N; j += i)
                vis[j] = 1;
        }
    }
}

void solve(ll id, ll n) {
    for(ll i = 0; i < tot; ++i) {
        while(n % prime[i] == 0) {
            n /= prime[i];
            a[i][id] ^= 1;
        }
    }
}

ll Gauss(ll equ,ll var){//返回自由变元个数
    /*初始化*/
    for(ll i=0;i<=var;i++){
        x[i]=0;
        freeX[i]=0;
    }

    /*转换为阶梯阵*/
    ll col=0;//当前处理的列
    ll num=0;//自由变元的序号
    ll row;//当前处理的行
    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){//col列第row行以下全是0,处理当前行的下一列
            freeX[num++]=col;//记录自由变元
            row--;
            continue;
        }

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

    /*求解*/
    //无解:化简的增广阵中存在(0,0,...,a)这样的行,且a!=0
    for(ll i=row;i<equ;i++)
        if(a[i][col]!=0)
            return -1;

    //无穷解: 在var*(var+1)的增广阵中出现(0,0,...,0)这样的行
    ll temp=var-row;//自由变元有var-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]);
    }
    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();
    ll t, n, kcase = 0;
    ll tmp;
    scanf("%lld", &t);
    while(t--) {
        scanf("%lld", &n);
        memset(a, 0, sizeof(a));
        for(ll i = 0; i < n; ++i) {
            scanf("%lld", &tmp);
            solve(i, tmp);
        }
        ll equ = tot, var = n;//equ个方程,var个变元
        ll freeNum = Gauss(equ, var);//自由元个数
        printf("Case #%lld:\n%lld\n", ++kcase, (qpow(2, freeNum) - 1 + mod) % mod);
    }
    return 0;
}

直接根据系数矩阵求自由变元的数量:https://blog.csdn.net/say_c_box/article/details/52208887

#include <iostream>
#include <cstdio>
#include <cmath>
#include <vector>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const ll mod = 1e9 + 7;
const int N = 2e3 + 10;
int a[N][N];
int prime[N], tot;
bool vis[N];

void init() {
    memset(vis, 0, sizeof(vis));
    vis[0] = vis[1] = 1;
    tot = 0;
    for(int i = 2; i < N; ++i) {
        if(!vis[i]) {
            prime[tot++] = i;
            for(int j = i + i; j < N; j += i)
                vis[j] = 1;
        }
    }
}

void solve(int id, ll n) {
    for(int i = 0; i < tot; ++i) {
        while(n % prime[i] == 0) {
            n /= prime[i];
            a[i][id] ^= 1;
        }
        if(n == 1) break;
    }
}

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;
}

ll Gauss(int m, int n) {///m * n矩阵
    int i = 0, j = 0, k, r, u;
    while(i < m && j < n) {
        r = i;
        for(k = i; k < m; ++k)
            if(a[k][j]) {r = k; break;}
        if(a[r][j]) {
            if(r != i)
                for(k = 0; k <= n; ++k)
                    swap(a[r][k], a[i][k]);
            for(u = i + 1; u < m; ++u) {
                if(a[u][j]) {
                    for(k = i; k <= n; ++k)
                        a[u][k] ^= a[i][k];
                }
            }
            ++i;
        }
        ++j;
    }
    return (qpow(2, (ll)(n - i)) - 1 + mod) % mod;
}

int main(){
    init();
    int n, t, kcase = 0;
    ll tmp;
    scanf("%d", &t);
    while(t--) {
        memset(a, 0, sizeof(a));
        scanf("%d", &n);
        for(int i = 0; i < n; ++i) {
            scanf("%lld", &tmp);
            solve(i, tmp);
        }
        printf("Case #%d:\n%lld\n", ++kcase, Gauss(tot, n));
    }
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值