题意:
在从1-n个数里选不超过m个数,至少选一个,这些数的乘积没有平方数因子(除了1),有多少种选法。
思路
大致是一个状态压缩+分组背包的问题。
其实还没怎么搞懂,先贴上代码,尝试写了一些注释,有问题可以留言交流
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<ctime>
#include "vector"
using namespace std;
#define maxn 505
#define mod 1000000007
#define inf 0x3f3f3f3f
#define ll long long
int p[]={2,3,5,7,11,13,17,19};
int sta[maxn],b[maxn],num[maxn],a[maxn][maxn];
//sta[i]表示i这个数的质因子状态 num存i这个数能有的状态个数
//b[i]表示i这个数除掉质因子后的那个数。a存第i组有哪些数
ll dp[maxn][maxn];//dp[i][j]表示取i个数,状态为j的方案数.
int n,m;
int main()
{
int t;
scanf("%d",&t);
while(t--){
scanf("%d%d",&n,&m);
memset(dp,0,sizeof(dp));
dp[0][0]=1;
for(int i=1;i<=n;i++){//初始化
num[i]=0;b[i]=i;sta[i]=0;
}
for(int i=1;i<=n;i++)//枚举每一个数的平方数的状态
for(int j=0;j<8;j++)
if(sta[i]!=-1&&i%p[j]==0&&i%(p[j]*p[j])!=0){
//如果这个数有这个质因子而且因子没有这个质因子的平方
//让这个数的状态上第j位的0变成1 就是说有第j个质因子
sta[i]|=1<<j;
//这个数除掉这个质因子;可以继续挑到别的质因子
b[i]/=p[j];
}else if(i%(p[j]*p[j])==0){
//如果这个数有平方数的因子,必暴毙
//直接break
sta[i]=-1;
break;
}
for(int i=1;i<=n;i++)
if(sta[i]!=-1){//如果这个数是一个非平方倍数
//这句话说明这个数是由一些质因子构成,因为最后除到1了,那么把这个数塞到a[i]组
//否则塞到a[b[i]]组(他最后变成的那个数)
//这里其实是开始分组
if(b[i]==1)a[i][num[i]++]=i;
else a[b[i]][num[b[i]]++]=i;
}
for(int i=1;i<=n;i++){//n组
if(sta[i]==-1||num[i]==0)continue;//如果这个数有平方数的因子或者这个组里面没东西
for(int j=m-1;j>=0;j--)//拿了几个东西
for(int k=0;k<(1<<8);k++)//状态
for(int q=0;q<num[i];q++)//组里面的东西
if((k&sta[a[i][q]])==0)//如果这个数与目前这个状态的二进制不冲突,也就是没有重复的质因子
//放了j个数,看二进制放了哪几个质因子
dp[j+1][k|sta[a[i][q]]]=(dp[j+1][k|sta[a[i][q]]]+dp[j][k])%mod;
}
ll ans=0;
//把所有的答案加上去
for(int i=1;i<=m;i++)
for(int j=0;j<(1<<8);j++)
ans=(ans+dp[i][j])%mod;
printf("%lld\n",ans);
}
return 0;
}