题意
有一个N*N的矩阵M,其中包含N*N正整数。现在要从这个矩阵中选出一些元素来,要求:
1)每行恰好选择了奇数个元素;
2)每列恰好选择了奇数个元素;
3)选出的所有元素的乘积是个完全平方数;
4)每个元素不能重复选择,即你不能选择M(i,j)多次。
问有多少种不同的选取方法。输出方法数mod 1,000,000,007后的答案。
1<=N<=20,1<=M[i][j]<=1,000,000,000。
分析
一开始没有往线性方程组上想,而是想的各种状压各种dp。。。
其实这题我们可以把每个数的素因子筛出来,算了算400个数大概会出现1000种不同的素因数。对每行每列和每个素因数都建立一个异或方程,然后对系数矩阵进行消元,最后答案就是2^(n*n-矩阵的秩)。因为有n*n个变量,只有(矩阵的秩)那么多个方程,那么自由元自然就有(n*n-矩阵的秩)那么多个啦。
因为方程有点多,可以用bitset来优化。
一开始写的高斯消元,答案不知道怎么的总会多1,后来改成用线性基来消元就过了。
代码
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<bitset>
using namespace std;
const int N=25;
const int MOD=1000000007;
int n,pri[N][N][35],tot[N][N],w[10005];
bitset<405> a[1505],bas[405];
int read()
{
int x=0,f=1;char ch=getchar();
while (ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while (ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
int point(int x,int y)
{
return (x-1)*n+y;
}
int gauss(int n,int m)
{
for (int i=1;i<=n;i++) bas[i].reset();
int s=0;
for (int i=1;i<=m;i++)
{
for (int j=n;j>=1;j--)
if (a[i][j])
{
if (!bas[j].count()) {bas[j]=a[i];s++;break;}
else a[i]^=bas[j];
}
if (a[i].count()==1&&a[i][n+1]) return -1;
}
return s;
}
int main()
{
int T=read();
while (T--)
{
n=read();
int w1=0;
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++)
{
int x=read();tot[i][j]=0;
for (int k=2;k*k<=x;k++)
if (x%k==0)
{
int s=0;
while (x%k==0) x/=k,s^=1;
if (s) pri[i][j][++tot[i][j]]=k,w[++w1]=k;
}
if (x>1) pri[i][j][++tot[i][j]]=x,w[++w1]=x;
}
sort(w+1,w+w1+1);w1=unique(w+1,w+w1+1)-w-1;
for (int i=1;i<=n*2+w1;i++) a[i].reset();
for (int i=1;i<=n;i++)
{
a[i][n*n+1]=a[i+n][n*n+1]=1;
for (int j=1;j<=n;j++)
{
a[i][point(i,j)]=a[j+n][point(i,j)]=1;
for (int k=1;k<=tot[i][j];k++)
{
int id=lower_bound(w+1,w+w1+1,pri[i][j][k])-w;
a[id+n*2][point(i,j)]=1;
}
}
}
int s=gauss(n*n,n*2+w1),ans=1;
if (s==-1) puts("0");
else
{
for (int i=1;i<=n*n-s;i++) ans=ans*2%MOD;
printf("%d\n",ans);
}
}
return 0;
}