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}。
【样例解释】
有8 个集合满足要求,分别是空集,{1},{1,4},{2},{2,3},{3},{3,4},{4}。
这道题很有意思,一开始也往dp那方面想了
但是这道题太妙了,又刷新了我的三观
这道题的关键在于,你把两个限制条件转化成矩阵:
1 3 9 27…
2 6 18 54…
4 12 36 108…
然后跑状压dp即可
代码如下:
#include<cmath> #include<cstdio> #include<cstring> #include<cstdlib> #include<algorithm> #define Mod 1000000001 using namespace std; typedef long long ll; bool v[210000]; int a[21][21]; ll f[21][210000]; int bin[21]; int n; ll solve(int x) { a[1][1]=x; int top; for(int i=1;i;i++) { if(i!=1) { a[i][1]=a[i-1][1]*2; if(a[i][1]>n){top=i-1;break;} } v[a[i][1]]=false; for(int j=2;j;j++) { a[i][j]=a[i][j-1]*3; if(a[i][j]>n){bin[i]=j-1;break;} v[a[i][j]]=0; } } bin[0]=1; for(int i=0;i<=top;i++) for(int j=0;j<(1<<bin[i]);j++) f[i][j]=0; bin[top+1]=0,f[top+1][0]=0; f[0][0]=1; for(int i=0;i<=top;i++) for(int j=0;j<(1<<bin[i]);j++) if(f[i][j]) { if(j&(j>>1))continue; for(int k=0;k<(1<<bin[i+1]);k++) { if(j&k)continue; f[i+1][k]=(f[i+1][k]+f[i][j])%Mod; } } return f[top+1][0]; } int main() { scanf("%d",&n); ll ans=1; memset(v,true,sizeof(v)); for(int i=1;i<=n;i++) if(v[i]==true) ans=(ans*(solve(i)%Mod))%Mod; printf("%d\n",ans); return 0; }
by_lmy