题目描述:
题目大意:
给出n和k,求从小于等于n的数中取出不超过k个,其乘积是无平方因子数的方案数。无平方因子数:不能被质数的平方整除。
样例输入:
3
1 1
6 4
4 2
样例输出:
1
19
6
题目大意:
状压dp+分组背包:
1~n中每个数含有的大于
n√
的质因数最多有1个,而
n=500−−−−−−√
内的质数只有8个:2,3,5,7,11,13,17,19。考虑这样将1~n的数分组,含有大于19的质因子数分为一组(如 23,46,69一组,如29,58一组),其余的单独成组。组内的每个元素用8位二进制表示该数含有的小于等于19的质因子。这样分组是因为小于等于n的质数只有8个可以方便的进行状压dp,而含有大于19的质因子的数难以在有限空间内状压,于是将其放在一个组内,dp时就只取出该组中的一个数,避免了乘积大于19的质因子。
因为代码用了vector,vector调用比较慢,出题人又专门卡常,所以需要一些优秀的卡常技巧。
附代码:
#include<iostream>
#include<cstring>
#include<string>
#include<cstdlib>
#include<cstdio>
#include<ctime>
#include<cmath>
#include<cctype>
#include<iomanip>
#include<algorithm>
#include<queue>
#include<vector>
using namespace std;
const int N=600;
const int M=(1<<8)-1;
const int mod=1e9+7;
const int prime[8]={2,3,5,7,11,13,17,19};
int f[N][M+10],num[N],exist[N],n,k,t,temp[N],ans;
vector<int> g[N];
int readint()
{
char ch;int i=0,f=1;
for(ch=getchar();(ch<'0'||ch>'9')&&ch!='-';ch=getchar());
if(ch=='-') {ch=getchar();f=-1;}
for(;ch>='0'&&ch<='9';ch=getchar()) i=(i<<3)+(i<<1)+ch-'0';
return i*f;
}
void prework()
{
for(int i=1;i<=n;i++)
{
int now=i;exist[i]=true;
for(int j=0;j<8;j++)
if(now%(prime[j]*prime[j])==0)//判断此数是否符合是无平方因子数
{
exist[i]=false;
break;
}
else
if(now%prime[j]==0)//表示这个数含有小于等于19的质因子
now/=prime[j],num[i]|=(1<<j);//记录状态,包含哪些小于等于19的质因子
if(exist[i])
{
if(now==1) g[i].push_back(i);//不含大于19的质因子,单独成组
else g[now].push_back(i); //前面每次都在除,还有剩余,说明含大于19的质因子,于是分到质因子那组
}
}
}
void add(int &x,int t)//为了卡常
{
x+=t;
if(x>=mod) x-=mod;
}
int main()
{
//freopen("mul.in","r",stdin);
//freopen("mul.out","w",stdout);
t=readint();
while(t--)
{
n=readint();k=readint();
memset(f,0,sizeof(f));
for(int i=1;i<=n;i++) g[i].clear();
prework();
f[0][0]=1;
for(int i=1;i<=n;i++)//其实是在枚举每一个分组
if(exist[i]&&g[i].size()!=0)//此数合法且此组不为空
{
for(int j=g[i].size()-1;j>=0;j--)//为了卡常
temp[j]=num[g[i][j]];
for(int l=k-1;l>=0;l--)//倒序,因为下面的更新,是把上一层的更新到现在的i,正序会让这一层覆盖掉上一层的结果
for(int j=g[i].size()-1;j>=0;j--)//枚举这一组
for(int s=(M^temp[j]),t=s;;s=((s-1)&t))//s表示状态,第一节是指只不选temp[j],第三节是在跳s的每个子集
{
add(f[l+1][s|temp[j]],f[l][s]);//f[l][s]存的是指上一层计算出来的结果
if(s==0) break;//子集为空break,之所以不放在for循环第二节,是因为我们需要更新一次子集为空的转移
}
}
ans=0;
for(int i=1;i<=k;i++)//统计各种情况和
for(int j=0;j<=M;j++)
add(ans,f[i][j]);
cout<<ans<<'\n';//printf("%d\n",ans);
}
return 0;
}