给定素因子不大于500的n(n<=100)个正整数,从中选出任意个,其乘积是完全平方数,问有都少中选法。
分析:若直接枚举所有子集,有2^n种难以承受,换种思路,我们可以用Xi来表示是否选第i个数 ,由于每个素因子必须是偶次幂,故可对每个素因子列出一个模2的方程。 然后问题就转换为求这个线性方程组的自由变量个数了(即方程个数 - 增广矩阵的秩)。
比如:n=2个数 8 =2^3 、 9 = 3^2
有两个素因子2和3,可列出两个方程:
3*X1 + 0*X2 = 0 (mod2) 等价于 : X1 +0*X2 = 0
0*X1 + 2*X2 = 0 (mod2) 0*X1 + 0*X2 = 0
其中只有1个有效方程,即秩为1。
这代表什么意思呢? X1 = 0 , 表示8一定不能选 , X2不确定,表示9可以选择也可以不选。
因此答案为 2^1 - 1 = 1 (因为不允许一个都不选,所以减一)
参考代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
typedef long long LL;
const int maxn = 110;
const int maxw = 4;
typedef unsigned int Mat[maxn][maxw];
const int max_pri = 500;
bool flag[max_pri+1];
int prime[max_pri / 4];
Mat m;
int table_prime(){
int cnt = 0 , m = sqrt(max_pri+0.5);
for(int i=2;i<=m;i++) if(!flag[i]) {
for(int j=i*i ; j<=max_pri ;j+=i) flag[j] = true;
}
for(int i=2;i<=max_pri;i++) if(!flag[i])
prime[cnt++] = i;
return cnt;
}
int Rank(Mat&m ,int R,int C){ //求矩阵的秩
int r=0,c=0,k,i,j,u,v;
while(r<R && c<C){
k = r , u=c/32 , v=c%32;
for(i=r+1;i<R;i++) {
if(m[i][u]&(1<<v)) {k=i; break; }
}
if(m[k][u]&(1<<v)){
if(k!=r) for(i=0;i<maxw;i++) swap(m[k][i] ,m[r][i]);
for(i=r+1;i<R;i++) if(m[i][u]&(1<<v)) {
for(j=0;j<maxw;j++) m[i][j] ^=m[r][j];
}
r++;
}
c++;
}
return r;
}
int main()
{
int M = table_prime();
int TT;
scanf("%d",&TT);
while(TT--){
int n , maxp = 0 , u,v;
LL x;
scanf("%d",&n);
memset(m,0,sizeof(m));
for(int i=0;i<n;i++){
u = i/32 , v = i%32;
scanf("%lld" , &x);
for(int k = 0;k<M;k++){
while(x%prime[k] == 0) {
x /= prime[k];
m[k][u] ^= (1<<v);
maxp = max(maxp , k+1);
}
}
}
int free = n - Rank(m,maxp,n);
LL ans = (1LL << free) - 1;
printf("%lld\n",ans);
}
return 0;
}