【BZOJ2734】集合选数(HNOI2012)-状压DP

测试地址:集合选数
做法:本题需要用到状压DP。
看到这种两个东西不能同时取的限制,显然就是个独立集,然而这题并不只是要求独立集,而是要求独立集的数量,所以我们就要观察这个图到底有什么性质。
注意到,一个点x会和2x3x连边,于是我们发现点x可以走到形如2p3qx这样的点,每一对(p,q)和一个点一一对应,而从(p,q)可以连接(p+1,q)(p,q+1),于是我们发现,这是一个网格图的定义,那么我们就知道,整个图是由若干个网格图组成的,因此我们只需分别计算各个网格图中独立集的数量,然后用乘法原理即可计算整个图的答案。
对于每一个不是23倍数的点,从它出发都有一个新的网格图,而因为pq都不会超过logn,所以网格图的边长就只有logn,因此可以用状压DP计算独立集数量,时间复杂度为O(nlogn)。对于每个网格图都计算一次,看上去复杂度很高,但根据实际计算,这个方法是可以通过此题的。
以下是本人代码:

#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;
}
posted @ 2018-06-11 20:25  Maxwei_wzj  阅读(112)  评论(0编辑  收藏  举报