题目大意:让你在1~n中选择不多于k个数(n,k<=500),保证它们的乘积不能被平方数整除。求选择的方案数
因为质数的平方在500以内的只有8个,所以我们考虑状压
先找出在n以内所有平方数小于等于n的质数,然后我们把它们作为状压的状态
然后要对每个小于n数进行状压,如果它不能被它能被质数的平方整除,那就筛出它所有的在状态内的质因子,大于状态内的质因子我们存到剩余因子的乘积的部分里
比如46,它的状态可以表示成0000 0001 (19,17,13,11,7,5,3,2) 46/2=23,把它存到23的0000 0001状态里
这么做之后,你会惊奇的发现,每个数字只被存到了一个地方,且只被存了一次!
而这也是分组背包可行的关键
下一步就是分组背包了
我们从1遍历到n,遍历所有状态,如果存了,意味着这个状态表示的数可以选一个,就++
然后枚举上一层进行转移即可
细节可以看代码
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <vector>
#define N 100
#define M 505
#define maxn (1<<8)+5
#define ll long long
#define mod 1000000007
using namespace std;
int T,n,m,cnt,K;
ll f[2][M][maxn],tmp[M][maxn];
int p[M],ok[M],pr[M],can[M][maxn],use[M];
int a[]={0,2,3,5,7,11,13,17,19,5000};
vector<int>to[M];
void clr()
{
for(int i=0;i<M;i++) to[i].clear();
memset(p,0,sizeof(p));
memset(ok,0,sizeof(ok));
memset(pr,0,sizeof(pr));
memset(can,0,sizeof(can));
memset(use,0,sizeof(use));
memset(f,0,sizeof(f));
cnt=0;
}
void get_P()
{
for(int i=2;i<=n;i++)
{
if(!use[i]) pr[cnt++]=i;
for(int j=0;j<cnt&&i*pr[j]<=n;j++){
use[i*pr[j]]=1;
if(i%pr[j]==0) break;}
}
for(int i=1;i<=n;i++)
{
int x=i,ps=0;
for(int j=0;j<min(K,8);j++)
if(x%(pr[j]*pr[j])==0) {
ok[i]=-1,p[i]=0;
break;
}else if(ok[i]!=-1&&x%pr[j]==0)
{
p[i]|=(1<<j);
x/=pr[j];
}
if(ok[i]!=-1)
{
if(x!=1) to[x].push_back(i);
else to[i].push_back(i);
}
}
for(int i=1;i<=n;i++) //can数组表示i是否存了能用某个二进制质因子表示的数
{
if(ok[i]==-1) continue;
for(int j=0;j<to[i].size();j++)
can[i][p[to[i][j]]]=1;
}
}
int main()
{
//freopen("aa.in","r",stdin);
scanf("%d",&T);
while(T--)
{
scanf("%d%d",&n,&m);
clr();
K=0;
while(K+1<=8&&a[K+1]*a[K+1]<=n) K++;
get_P();
int y=1,x=0;
for(int i=1;i<=n;i++){ //分组背包
if(!to[i].size()) continue;
for(int s1=0;s1<(1<<K);s1++)
for(int j=1;j<=m;j++)
f[y][j][s1]=f[x][j][s1];
for(int s1=0;s1<(1<<K);s1++)
{
if(!can[i][s1]) continue;
f[y][1][s1]=(f[y][1][s1]+1)%mod;
//如果i的状态里有s1,那么说明这个状态表示的数可以直接被选,就++
for(int j=1;j<=m;j++)
for(int s2=0;s2<(1<<K);s2++)
{
if(s1&s2) continue;
f[y][j][s1|s2]+=f[x][j-1][s2];
f[y][j][s1|s2]%=mod;
}
}
swap(y,x);
}
ll ans=0;
for(int s=0;s<(1<<K);s++)
for(int j=1;j<=m;j++)
ans=(ans+f[x][j][s])%mod;
printf("%lld\n",ans);
}
return 0;
}