Zhu and 772002
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/65536 K (Java/Others)
Total Submission(s): 2637 Accepted Submission(s): 944
Problem Description
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 n numbers a1,a2,...,an. The value of the prime factors of each number does not exceed 2000, you can choose at least one number and multiply them, then you can get a number b.
How many different ways of choices can make b is a perfect square number. The answer maybe too large, so you should output the answer modulo by 1000000007.
Input
First line is a positive integer T , represents there are T test cases.
For each test case:
First line includes a number n(1≤n≤300),next line there are n numbers 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 1000000007.
Sample Input
2
3
3 3 4
3
2 2 2
Sample Output
Case #1:
3
Case #2:
3
Author
UESTC
Source
题目大意:给你n个数,选择其中的一些数(至少选择一个)使得它们的乘积是一个完全平方数,问总的方案模上1000000007的结果。
题解:看看完全平方数的特点:每个质因数的个数为偶数,比如4=2*2 ,质因数2有偶数个,36=2*2*3*3,质因数2和3都有偶数个,因此需要把每个数分解为质因数形式,每个数有取和不取两种情况,使用搜索进行组合判断是一个方案,但是暴力搜索一定会超时,这时就需要用一种新的方法解决:线性方程组,b[i][j]是这个线性方程组的系数矩阵,b[i][j]表示第j个数的第i个因子为偶数(用0表示)还是为奇数(用1表示),求解这个线性方程组的自由元的个数即可解决问题(自由元是指这个未知数取任何值都行,此问题就是取和不取两种值),因此结果是 2^(自由元个数)-1 ,使用快速模幂法求解此式子。自由元的个数如何求解,可以使用高斯消元法进行求解,具体看代码:
AC的C++程序:
#include<iostream>
#include<cstring>
using namespace std;
typedef long long ll;
//快速模幂法
ll f(ll x,ll n)
{
ll res=1;
while(n>0)
{
if(n&1)
res=res*x%1000000007;
x=x*x%1000000007;
n>>=1;
}
return res;
}
int p[310];//不超过2000的素数只有304个
int b[310][310];//a[i][j]表示第j个数的第i个因子的系数
int len=0;
void init() //用p数组存储连续素数
{
bool a[2005]={false};
a[0]=a[1]=false;
for(int i=2;i<=2000;i++)
if(!a[i])
{
p[len++]=i;
for(int j=i*i;j<=2000;j+=i)
a[j]=true;
}
}
ll Guass(int m,int n)//m个等式,n个变量
{
int i=0,j=0;//i标记行,j标记列
while(i<m&&j<n)
{
int r;
//找到j列第一个不为0的行
for(r=i;r<m&&b[r][j]==0;r++);
if(b[r][j])//如果在第j列找到了不为0的行
{
if(r!=i)//r不等于i,就把第r行和第i行进行交换
for(int k=0;k<n;k++)
swap(b[i][k],b[r][k]);
//把i行以下的行进行变零操作
for(int k=i+1;k<m;k++)
if(b[k][j])//如果为零就不用进行操作,非零就要进行异或操作
for(int t=0;t<n;t++)
b[k][t]^=b[i][t];//^为异或运算符
i++;//此行操作完,接着操作下一行
}
j++;
}
return f(2,n-i)-1;//结果
}
int main()
{
init();
int t,n;
scanf("%d",&t);
for(int i=1;i<=t;i++)
{
memset(b,0,sizeof(b));
scanf("%d",&n);
for(int j=0;j<n;j++)
{
ll x;
scanf("%lld",&x);
//进行分解,分解为质因数乘积形式
for(int k=0;k<len;k++)
while(x%p[k]==0)
{
b[k][j]^=1;
x/=p[k];
}
}
printf("Case #%d:\n%lld\n",i,Guass(len,n));
}
return 0;
}