携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第27天,点击查看活动详情
[HNOI2012]集合选数
题目描述
《集合论与图论》这门课程有一道作业题,要求同学们求出 的所有满足以下条件的子集:若 在该子集中,则 和 不能在该子集中。
同学们不喜欢这种具有枚举性质的题目,于是把它变成了以下问题:对于任意一个正整数 ,如何求出 的满足上述约束条件的子集的个数(只需输出对 取模的结果),现在这个问题就交给你了。
输入格式
只有一行,其中有一个正整数 。 的数据满足 。
输出格式
仅包含一个正整数,表示 有多少个满足上述约束条件的子集。
样例 #1
样例输入 #1
4
样例输出 #1
8
提示
【样例解释】
有 个集合满足要求,分别是空集,,,,,,,。
【数据范围】
对于 的数据,。
对于 的数据,。
#include<bits/stdc++.h>
using namespace std;
#define re register
#define int long long
const int maxn=100010;
const int mod=1000000001;
int n,ans=1,res,last;
int line[20],lim[20];
int a[20][20],f[2][1<<20];
bool check[1<<20],vis[maxn];
inline void build(int x)
{
for(re int i=1;i<=11;i++)
{
if(i==1)a[i][1]=x;
else a[i][1]=a[i-1][1]*3;
if(a[i][1]>n)break;
last=i,line[i]=1,vis[a[i][1]]=1;
for(re int j=2;j<=18;j++)
{
a[i][j]=a[i][j-1]*2;
if(a[i][j]>n)break;
line[i]=j;vis[a[i][j]]=1;
}
lim[i]=(1<<line[i])-1;
}
//printf("case::%d :%d\n",x,last);
}
inline void solve(int x)
{
res=0;int now=1;
for(re int i=0;i<=lim[1];i++)f[1][i]=check[i];
for(re int i=2;i<=last;i++)
{
/*for(int j=0;j<=lim[i-1];j++)printf("%d ",f[now^1][j]);
puts("");*/
now=i&1;
for(re int j=0;j<=lim[i];j++)
{
f[now][j]=0;
if(!check[j])continue;
for(re int k=0;k<=lim[i-1];k++)
{
if(check[k]&&!(k&j))f[now][j]=(f[now][j]+f[now^1][k])%mod;
}
}
}
//printf("now::%d\n",now);
//for(int i=0;i<=lim[last];i++)if(check[i])printf("%d\n",i);
for(re int i=0;i<=lim[last];i++)if(check[i])res=(res+f[now][i])%mod;
//printf("res::%d\n",res);
}
signed main()
{
scanf("%lld",&n);
for(re int i=0;i<(1<<18);i++)check[i]=((i<<1)&i)?0:1;
for(re int i=1;i<=n;i++)
{
if(vis[i])continue;
build(i),solve(i);
if(res)ans=(ans*res)%mod;
}
printf("%lld",ans);
return 0;
}