BZOJ 2734[HNOI2012]集合选数

题意: 给一个n,求{1~n}的所有满足以下条件的子集:若 x 在该子集中,则 2x 和 3x 不能在该子集中。

很神奇的一道题,刚开始以为是数学,还想了一下筛法什么的。。。但是只要稍加思索就会发现,对于一个元素,

有取与不取两个状态,如果用01状态表示取与不取,就是一个状压dp了,再看一看复杂度,靠谱。

但是怎样满足题设条件呢???

表示不会搜了题解,然后发现这题思想神的一逼:

    对于一个数x,构造一个矩阵

    x,3x,9x......

    2x,6x,18x......

    4x,12x,36x......

于是就会发现这道题神奇的转化成了经典问题:互不侵犯king,上下左右相邻数不可同时取到,用状压搞之,过了。

notice:数组计算好,是18*11的,有个同学,开数组开大了,空间上可以过,但是用memset清零超时了,一定不要多开!!!!!

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
typedef long long ll;
const int MOD=1000000001;
bool pd[100001];
int map[20][20],n,s[20],base[20];
ll dp[20][1<<11],ans=1ll;
inline void make(int x)
{
    memset(map,0,sizeof(map));
    for(int i=1,h=x;h<=n;i++,h=h*2)
    {
        map[i][1]=h;
        pd[h]=1;
        for(int j=2,z=h*3;z<=n;j++,z=z*3)
        {
            map[i][j]=z;
            pd[z]=1;
        }
    }
    memset(s,0,sizeof(s));
    for(int i=1;i<=18;i++)
        for(int j=1;j<=11;j++)
            if(map[i][j]!=0)
            {
                pd[map[i][j]]=1;
                s[i]+=base[j-1];
            }
}
inline int DP(int x)
{
    memset(dp,0,sizeof(dp));
    dp[0][0]=1;
    for(int i=0;i<18;i++)
        for(int j=0;j<=s[i];j++)
            if(dp[i][j])
                for(int k=0;k<=s[i+1];k++)
                    if((!(j&k))&&(!(k&(k>>1))))
                        dp[i+1][k]=(dp[i][j]+dp[i+1][k])%MOD;
    return dp[18][0];
}
inline void solve()
{
    for(int i=1;i<=n;i++)
        if(!pd[i])
        {
            make(i);
            ans=ans*DP(i)%MOD;
        }
    printf("%lld",ans%MOD);
}
int main()
{
    base[0]=1;
    for(int i=1;i<20;i++)base[i]=(base[i-1]<<1);
    scanf("%d",&n);
    solve();
}

 

posted @ 2015-12-30 18:59  聂渣渣  阅读(166)  评论(0编辑  收藏  举报