题目描述
《集合论与图论》这门课程有一道作业题,要求同学们求出{1, 2, 3, 4, 5}的所有满足以 下条件的子集:若 x 在该子集中,则 2x 和 3x 不能在该子集中。
同学们不喜欢这种具有枚举性 质的题目,于是把它变成了以下问题:对于任意一个正整数 n<=100000,如何求出{1, 2,…, n} 的满足上述约束条件的子集的个数(只需输出对 1,000,000,001 取模的结果),现在这个问题就 交给你了。
输入格式
只有一行,其中有一个正整数 n,30%的数据满足 n<=20。
输出格式
仅包含一个正整数,表示{1, 2,…, n}有多少个满足上述约束条件 的子集。
输入输出样例
输入
4
输出
8
说明/提示
【样例解释】
有8 个集合满足要求,分别是空集,{1},{1,4},{2},{2,3},{3},{3,4},{4}。
思路(状压DP)
-
题目要求“若 x 在该子集中,则 2x 和 3x 不能在该子集中”,那么我们就可以构造数个矩阵。
-
这些矩阵中没有重复的数,且保证了它们的并集取到了从1到n。
-
其中每个矩阵都满足对于矩阵中任意一个数,都满足它是它左边那个数的3倍,同时是上面那个数的2倍,即
a[i][j]=a[i-1][j]*2=a[i][j-1]*3
,如图: -
那么题目就变成了从所构造的矩阵当中取出若干个小于n的数的方法种数,就是典型的状压DP了。
-
由于有多个矩阵,最终答案即为各个矩阵所得到的答案之积。
小细节
- 行间是3倍,列间是2倍,这样可以减少每行的状态数,降低时间空间复杂度。
- 为了保证所有的矩阵中没有重复的数,每次构造矩阵都从未被用过的数中选取最小的一个作为矩阵的a[1][1]。
- 为了使取出的数满足小于等于n,可以通过判断状态所取到的最高位是否大于n,若大于n,则可直接退出(状态是单增的)。
- 但是构造矩阵是不满足小于等于n的 数也一样要计算出,确保上述判断有效。
- 注意开
long long
,能模就模!
代码
#include<cstdio>
#include<cmath>
#define int long long
#define ri register int
const int maxn=1e5+9;
const int p=1e9+1;
int a[20][15],b[20][15];
int s[maxn],lg[maxn];
int dp[20][maxn];
int flag[maxn];
int n,ans=1;
void js(){
int cnt=0;
for(ri i=0;i<maxn;++i)
{
if(a[1][lg[i]]>n) break;
if(!(i&(i>>1))) s[++cnt]=i;
}
for(ri j=1;j<=cnt;++j) dp[1][j]=1;
for(int i=2;i<=18;++i)
{
if(a[i][1]>n) break;
for(int j=1;j<=cnt;++j)
{
if(a[i][lg[s[j]]]>n) break;
for(int k=1;k<=cnt;++k)
{
if(!(s[j]&s[k])) dp[i][j]=(dp[i][j]+dp[i-1][k])%p;
if(a[i-1][lg[s[k]]]>n) break;
}
}
}
int t=0;
for(ri i=1;i<=18;++i)
{
if(a[i+1][1]<=n) continue;
for(ri j=1;j<=cnt;++j)
if(a[i][lg[s[j]]]<=n)
t=(t+dp[i][j])%p;
break;
}
if(t) ans=ans*t%p;
for(ri i=1;i<=18;++i)
for(ri j=1;j<=cnt;++j)
dp[i][j]=s[j]=0;
}
void get_a(int I){
for(ri i=1;i<=18;++i)
for(ri j=1;j<=12;++j)
{
a[i][j]=b[i][j]*I;
if(a[i][j]<=n) flag[a[i][j]]=1;
}
}
void solve(){
scanf("%lld",&n);
b[1][1]=1;
for(ri i=2;i<=12;++i) b[1][i]=b[1][i-1]*3;
for(ri i=1;i<=maxn;++i) lg[i]=lg[i-1]+(1<<lg[i-1]==i);
for(ri i=2;i<=18;++i)
for(ri j=1;j<=12;++j)
b[i][j]=b[i-1][j]*2;
for(ri I=1;I<=n;++I)
{
if(flag[I]) continue;
get_a(I);
if(a[1][1]>n) break;
js();
}
printf("%lld\n",ans);
}
#undef int
int main(){
solve();
return 0;
}