2017.10.24 T2 2008
样例数据
输入
3
1 1
6 4
4 2
输出
1
19
6
分析:状压DP+分组背包
每一个数只会含有一个比sqrt(N)大的质因数,所以我们把所有数按照有比sqrt(N)大的数分组,
sqrt(N)以内的质数只有8个。
然后dp[i][j][mask]表示计算完了前i组数,选择了j个数,mask表示当前乘积含有比sqrt(N)小的质数的集合。
然后进行背包就好了。
这道题70%的档本来是给想到状压dp但是没有用背包优化的代码的,但是30以内的情况最多就只有6655个,跑个剪枝dfs也能过,打表也能过。
但是我的代码有毒,在windows环境下编译没问题,到linux环境就爆数组了!看来考试的时候真的要开一下虚拟机跑一跑……活活又少了70分,这样,这次考试就只有0+0+20=20,爆炸得不行,必须熟悉一下linux环境有哪些Do’s and Don’ts了。
代码:
70%:暴搜剪枝
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<ctime>
#include<cmath>
#include<algorithm>
#include<cctype>
#include<iomanip>
#include<queue>
#include<set>
using namespace std;
int getint()
{
int sum=0,f=1;
char ch;
for(ch=getchar();!isdigit(ch)&&ch!='-';ch=getchar());
if(ch=='-')
{
f=-1;
ch=getchar();
}
for(;isdigit(ch);ch=getchar())
sum=(sum<<3)+(sum<<1)+ch-48;
return sum*f;
}
const int mo=1e9+7;
int T,n,k,ans;
int cnt,zhishu[100],num[510],mul[100];
bool bj[510];
void pre()
{
for(int i=2;i<=500;++i)
{
if(!bj[i])
{
zhishu[++cnt]=i;
for(int j=2;i*j<=500;++j)
bj[i*j]=true;
}
num[i]=cnt;
}
for(int i=1;i<=cnt;++i)
mul[i]=zhishu[i]*zhishu[i];//算每个质数的平方
}
bool check(long long w)
{
for(int i=1;i<=cnt;++i)//看看这个数能否被某个质数的平方整除
if(w%mul[i]==0)
return false;
return true;
}
void dfs(int fa,int step,long long w)//fa:前一个数,step:第几个,w:前面数的乘积
{
if(step>k)
return;
for(int i=fa+1;i<=n;++i)
{
if(check(i*w))//只有还满足题意的情况才能往下dfs
{
ans=(ans+1)%mo;
dfs(i,step+1,i*w);
}
}
}
int main()
{
freopen("mul.in","r",stdin);
freopen("mul.out","w",stdout);
pre();//找质数
T=getint();
while(T--)
{
ans=0;
n=getint(),k=getint();
if(n<=30)
{
dfs(0,1,1);
cout<<ans<<'\n';
}
else
cout<<rand()%mo<<'\n';//大于直接随机了hhh
}
return 0;
}
100%:状压dp+多组背包
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<ctime>
#include<cmath>
#include<algorithm>
#include<cctype>
#include<iomanip>
#include<queue>
#include<set>
using namespace std;
int getint()
{
int sum=0,f=1;
char ch;
for(ch=getchar();!isdigit(ch)&&ch!='-';ch=getchar());
if(ch=='-')
{
f=-1;
ch=getchar();
}
for(;isdigit(ch);ch=getchar())
sum=(sum<<3)+(sum<<1)+ch-48;
return sum*f;
}
const int maxn=505;
const int maxm=(1<<8)-1;
const int MAX=maxm+5;
const int mod =1e9+7;
int dp[maxn][MAX],num[maxn],exist[maxn];
int T,n,k,prime[8]={2,3,5,7,11,13,17,19};
vector<int> g[maxn];
void init()
{
for(int i=1;i<=n;++i)
{
int now=i;
exist[i]=1;
for(int j=0;j<8;++j)
if(now%(prime[j]*prime[j])==0)//把那些本来就含有质数平方的数剔除
{
exist[i]=0;
break;
}
else if(now%prime[j]==0)//二进制标记含有哪些质因子
now/=prime[j],num[i]|=(1<<j);
if(exist[i])//满足的数放到vector数组中,也就是背包,余下什么就在哪个背包里
{
if(now==1) g[i].push_back(i);//只剩下1的就放在自己本身位置背包
else g[now].push_back(i);//这是有质数大于sqrt(N)的情况,就放在对应的质数位置背包里
}
}
}
void add(int &x, int t)//为什么不写成(x+t)%mod?因为也会被卡常,这样快一些
{
x=x+t;
if(x>=mod)
x-=mod;
else
x-=0;
}
int main()
{
freopen("mul.in","r",stdin);
freopen("mul.out","w",stdout);
T=getint();
while(T--)
{
n=getint(), k=getint();
memset(dp,0,sizeof(dp));
for(int i=1;i<=n;++i) g[i].clear();
init();
dp[0][0]=1;//我写的dp省略了第i组那一维,不影响
for(int i=1;i<=n;++i)
if(exist[i]&&g[i].size()!=0)//背包dp,看这组是否合法
{
int temp[maxn];
for(int j=g[i].size()-1;j>=0;--j)
temp[j]=num[g[i][j]];//这步操作只是把这一组的所有数从vector数组里拿出来,因为调用vector很慢,被卡常
for(int l=k-1;l>=0;--l)//从后往前更新,因为上一个数更新了所有的状态,如果这个数从前往后就把前一个数更新的值覆盖了,变成自己更新自己了,所以要从后面开始更新
for(int j=g[i].size()-1;j>=0;--j)//枚举该组所有成分
for(int s=(maxm^temp[j]),t=s;;s=((s-1)&t))//s是和现在枚举的成分没有公共质因数的状态,更新s及其每个子集
{
add(dp[l+1][s|temp[j]],dp[l][s]);//更新状态
if(s==0) break;
}
}
int ans=0;
for(int i=1;i<=k;++i)
for(int j = 0;j<=maxm;++j)
add(ans,dp[i][j]);//从1—k个数所有情况加起来得ans
cout<<ans<<'\n';
}
return 0;
}
本题结。