【BZOJ2734】集合选数(HNOI2012)-状压DP
测试地址:集合选数
做法:本题需要用到状压DP。
看到这种两个东西不能同时取的限制,显然就是个独立集,然而这题并不只是要求独立集,而是要求独立集的数量,所以我们就要观察这个图到底有什么性质。
注意到,一个点会和和连边,于是我们发现点可以走到形如这样的点,每一对和一个点一一对应,而从可以连接和,于是我们发现,这是一个网格图的定义,那么我们就知道,整个图是由若干个网格图组成的,因此我们只需分别计算各个网格图中独立集的数量,然后用乘法原理即可计算整个图的答案。
对于每一个不是或倍数的点,从它出发都有一个新的网格图,而因为和都不会超过,所以网格图的边长就只有,因此可以用状压DP计算独立集数量,时间复杂度为。对于每个网格图都计算一次,看上去复杂度很高,但根据实际计算,这个方法是可以通过此题的。
以下是本人代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=1000000001;
int n;
ll f[2][200010],ans=1;
bool vis[400010];
bool check(int x)
{
int last=0;
while(x)
{
if (last&&(x&1)) return 1;
last=x&1;
x>>=1;
}
return 0;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=(n<<2);i++)
vis[i]=check(i);
for(int i=1;i<=n;i++)
if (i%2&&i%3)
{
int now=1,past=0;
f[0][0]=1;
int last=0;
for(int j=i;j<=n;j*=3)
{
int x=0,y=1;
while(j*y<=n) x++,y<<=1;
for(int k=0;k<(1<<x);k++)
{
f[now][k]=0;
if (vis[k]) continue;
for(int l=0;l<(1<<last);l++)
if (!(k&l)) f[now][k]=(f[now][k]+f[past][l])%mod;
}
swap(now,past);
last=x;
if (j*3>n)
{
ll tot=0;
for(int k=0;k<(1<<x);k++)
tot=(tot+f[past][k])%mod;
ans=ans*tot%mod;
}
}
}
printf("%lld",ans);
return 0;
}