[HNOI2012]集合选数


[HNOI2012]集合选数

很好的题啊!
首先看到数据范围无从下手,数论?容斥?反正死也想不到状压...
这里有一个很巧妙的转换.

[egin{matrix} 1 & 2 & 4 & 8 & ...\ 3 & 6 & 12 & 24 & ...\ 9 & 18 & 36 & 72 & ...\ 27 & 54 & 108 & 216 & ... end{matrix} ]

你会发现这个矩阵你不能同时选相邻的两个数,而这个矩阵的大小最大只有16,那么显然可以状压了.当然这个矩阵不会包含所有的数,于是枚举所有的这样的矩阵做dp.
状压一般都可以减掉很多枝的,跑得过去,复杂度玄学.

#include<bits/stdc++.h>
#define ll long long
#define mod 1000000001
using namespace std;
int dp[2][1<<12],lim[20],n,num[20],cur,pre;
ll mat[20][20],ans=1;
void init(int x)
{
    mat[1][1]=x;memset(num,0,sizeof(num));memset(lim,0,sizeof(lim));
    for(int i=2;i<=17;i++)mat[i][1]=mat[i-1][1]*2;
    for(int i=2;i<=12;i++)
        for(int j=1;j<=17;j++)
            mat[j][i]=mat[j][i-1]*3;
    for(int i=1;i<=17;i++)
        for(int j=1;j<=12;j++)
        {
            if(mat[i][j]<=n)lim[i]^=1<<(j-1),num[i]=j;
            else break;
        }
}
int DP(int x)
{
    int res=0;cur=1;pre=0;
    memset(dp,0,sizeof(dp));
    dp[cur][0]=1;init(x);//cout<<x<<endl;
    for(int i=0;i<17;i++)
    {
        //cout<<lim[i]<<" ";
        for(int j=0;j<(1<<num[i]);j++)
        {
            if(!dp[cur][j])continue;
            int t=dp[cur][j];dp[cur][j]=0;
            if(j&(j<<1))continue;
            if((j&lim[i])!=j)continue;
            //cout<<i<<" "<<j<<" "<<t<<endl;
            for(int k=0;k<(1<<num[i+1]);k++)
            {
                if(k&j)continue;
                dp[pre][k]+=t;dp[pre][k]%=mod;
            }
        }
        swap(pre,cur);
    }
    for(int i=0;i<(1<<12);i++)res+=dp[cur][i],res%=mod;
    //cout<<res<<endl;
    return res;
}
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
        if((i%2)&&(i%3))ans*=DP(i),ans%=mod;
    cout<<ans<<endl;
    return 0;
}

原文地址:https://www.cnblogs.com/terribleterrible/p/9833553.html