UVa11542Squre——异或方程组&&高斯消元法

题意

给出 $n$ 个整数,从中选出1个或多个,使得选出的整数乘积是完全平方数。一共有多少种选法?($1 leq n leq 100$,$1 leq a_i leq 10^{15}$ 且不含大于500的素因子)

分析

“不含大于500的素因子”提示我们考虑每个数的素因数分解,用01向量表示一个数,再用 $n$ 个01变量 $x_i$ 来表示我们的选择,其中 $x_i=1$ 表示要选第 $i$ 个数,$x_i=0$ 表示不选第 $i$ 个数,则对每个素数的幂列出一个模2的方程。

如果要让这个数是完全平方数,每个幂都应该是偶数,即模2为0.

在模2的剩余系中每个数都是0/1,加法可换成异或,于是就成了一个异或方程组。

xor方程组很好消元的,因为不需要做乘法和除法,只需要做xor;每次也不需要找绝对值最大的系数(因为系数不是0就是1,只需系数为1即可消元)

设*变量为 $r$,则最终答案为 $2^r-1$,减1是因为本题不允许一个整数都不选。

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const int maxn = 500 + 10;

typedef int Matrix[maxn][maxn];

//m个方程,n个变量
int Rank(Matrix A, int m, int n)    //求矩阵的秩
{
    int i = 0, j = 0, k, r, u;
    while(i < m && j < n)      //当前正在处理第i个方程的第j个变量
    {
        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 i;
}

//返回n以内素数的个数
//埃氏筛法O(nloglogn)
const int maxs = 500 + 10;
int prime[maxs];            //prime[i]表示第i个素数
bool is_prime[maxs + 1];    //is_prime[i]为true表示i是素数

int sieve(int n)
{
    int cnt = 0;
    for (int i = 0; i <= n; i++)  is_prime[i] = true;
    is_prime[0] = is_prime[1] = false;
    for (int i = 2; i <= n; i++)
    {
        if (is_prime[i])
        {
            prime[cnt++] = i;
            for (int j = i * i; j <= n; j += i)  is_prime[j] = false;  //i * i可能爆int
        }
    }
    return cnt;
}

Matrix A;

int main()
{
    int m = sieve(500);

    int T;
    scanf("%d", &T);
    while(T--)
    {
        int n, max_p=0;
        ll x;
        scanf("%d", &n);
        memset(A, 0, sizeof(A));       //记得置零
        for(int i = 0;i < n;i++)
        {
            scanf("%lld", &x);
            for(int j = 0;j < m;j++)
            {
                while(x % prime[j] == 0)
                {
                    max_p = max(max_p, j);
                    x /= prime[j];
                    A[j][i] ^= 1;
                }
            }
        }
        int r = Rank(A, max_p+1, n);
        printf("%lld
", (1LL << (n-r))-1);
    }
    return 0;
}

From:

《算法竞赛入门经典训练指南》——刘汝佳、陈锋著