您的位置:首页 > 其它

【bzoj2734】【HNOI2012】【集合选数】【状压dp】

2016-03-31 18:39 302 查看

Description

《集合论与图论》这门课程有一道作业题,要求同学们求出{1, 2, 3, 4, 5}的所有满足以 下条件的子集:若 x 在该子集中,则 2x 和 3x 不能在该子集中。同学们不喜欢这种具有枚举性 质的题目,于是把它变成了以下问题:对于任意一个正整数 n≤100000,如何求出{1, 2,..., n} 的满足上述约束条件的子集的个数(只需输出对
1,000,000,001 取模的结果),现在这个问题就 交给你了。 

 

Input

 只有一行,其中有一个正整数 n,30%的数据满足 n≤20。 

 

Output

 仅包含一个正整数,表示{1, 2,..., n}有多少个满足上述约束条件 的子集。 

 

Sample Input

4

Sample Output

8

【样例解释】

有8 个集合满足要求,分别是空集,{1},{1,4},{2},{2,3},{3},{3,4},{4}。

题解:我们把x之间的关系写成一些矩阵。

x 3x 9x ...

2x 6x 18x ...

4x 12x 24x ...

...

现在问题变成了给你一些矩阵,要求不能选相邻的数,求方案数。

显然这个矩阵的长宽是log级的。

所以对于每个矩阵直接状压dp

最后把所有矩阵的答案乘起来即可。

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#define P 1000000001
using namespace std;
int f[2][1<<12],n,vis[1<<12];
long long ans(1);
int cal(int x){
int i,j,t,a,b,pre(0),ans(0);
memset(f,0,sizeof(f));
f[1][0]=1;
for (i=0;x*(1<<i)<=n;i++){
memset(f+(i&1),0,sizeof(f)>>1);
for (j=0,t=1;x*t*(1<<i)<=n;j++,t*=3);
for (a=0;a<(1<<pre);a++)
if (vis[a])
for (b=0;b<(1<<j);b++)
if (vis[b]&&((a&b)==0))
f[i&1][b]+=f[~i&1][a],f[i&1][b]%=P;
pre=j;
}
//cout<<f[0][0]<<' '<<f[0][1]<<endl;
for (j=0;j<(1<<pre);j++)
ans+=f[~i&1][j],ans%=P;
return ans;
}
int main(){
scanf("%d",&n);
for (int i=0;i<(1<<12);i++)
if (!(i<<1&i)&&!(i>>1&i))
vis[i]=1;
for (int i=1;i<=n;i++)
if (i%2&&i%3)
ans*=cal(i),ans%=P;
cout<<ans<<endl;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: