[题解] BZOJ 2734 [HNOI2012] 集合选数

BZOJ 2734 [HNOI2012] 集合选数

题目描述 Description
《集合论与图论》这门课程有一道作业题,要求同学们求出{1,2,3,4,5}的所有满足以下条件的子集:若x在该子集中,则2x和3x不能在该子集中.同学们不喜欢这种具有枚举性质的题目,于是把它变成了以下问题:对于任意一个正整数n,如何求出{1,2,…,n}的满足上述约束条件的子集的个数

输入描述 Input Description
只有一行,其中有一个正整数n

输出描述 Output Description
仅包含一个正整数,表示{1,2,…,n}有多少个满足上述约束条件的子集,答案对1,000,000,001取模

样例输入 Sample Input
4

样例输出 Sample Output
8

数据范围及提示 Data Size & Hint
对于30%的数据, 1 <= n <= 20
对于100%的数据 1 <= n <= 100000

Solution

不难发现,其实很多数之间都是相互独立的,我们就把这些数单独取出来

对于每个数x来说,2x,3x都不能选,那么就搞一个 x 2 q 3 p 的集合出来单独选就好了
对于每个集合,最多有 l o g 2 n l o g 3 n 个元素
然后对于每个矩阵DP,最后将不同矩阵利用乘法原理乘起来

矩阵DP细节在代码里,自己看看就好

代码如下:

#include <bits/stdc++.h>
using namespace std;
const int mod = 1000000001;
long long tot=1;
int n;
int bin[20];
int a[20][20];
int b[20];//表示每一行有哪些数可以选
int f[20][2048];
bool vis[100005];
int read() {
    int ans=0,flag=1;
    char ch=getchar();
    while((ch<'0' || ch>'9') && ch!='-') ch=getchar();
    if(ch=='-') { flag=-1;ch=getchar(); }
    while(ch>='0' && ch<='9') { ans=ans*10+ch-'0';ch=getchar(); }
    return ans*flag;
}
int cal(int x) {
    for(int i=1;i<=18;i++) b[i]=0;
    a[1][1]=x;
    for(int i=2;i<=18;++i) {
        if((a[i-1][1]<<1)<=n) a[i][1]=(a[i-1][1]<<1);
        else a[i][1]=n+1;//为什么要设为n+1而不是直接退出呢?因为后面计算b[i]的时候要用
    }
    for(int i=1;i<=18;++i)
        for(int j=2;j<=11;++j) {
            if(a[i][j-1]*3<=n) a[i][j]=a[i][j-1]*3;
            else a[i][j]=n+1;//同上
        }
    for(int i=1;i<=18;++i)
        for(int j=1;j<=11;++j)
            if(a[i][j]<=n) {//若之前的a[i][j]直接退出,则后面的数都为0,都会被计算入<=n当中
                //其实把bin做成前缀和然后特判0也可以QwQ
                b[i]+=bin[j-1];
                vis[a[i][j]]=1;
            }
    for(int i=0;i<=18;i++)
        for(int j=0;j<=b[i];++j)
            f[i][j]=0;
    f[0][0]=1;
    for(int i=0;i<18;++i)
        for(int j=0;j<=b[i];++j)
            if(f[i][j])
                for(int k=0;k<=b[i+1];k++)
                    if(((j&k)==0) && ((k&(k>>1))==0))
                        f[i+1][k]=(f[i+1][k]+f[i][j])%mod;
    //直接枚举两个状态(当前/下一行)进行转移
    //非常的简单
    return f[18][0];
}
int main() {
    bin[0]=1;//bin[i]=(1<<i)
    for(int i=1;i<20;i++)
        bin[i]=(bin[i-1]<<1);
    n=read();
    for(int i=1;i<=n;++i)
        if(!vis[i]) {
            tot*=cal(i);
            tot%=mod;
        }
    printf("%d\n",tot);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/force_chl/article/details/80502177