[HNOI2012]集合选数

题目描述

《集合论与图论》这门课程有一道作业题,要求同学们求出{1, 2, 3, 4, 5}的所有满足以 下条件的子集:若 x 在该子集中,则 2x 和 3x 不能在该子集中。

同学们不喜欢这种具有枚举性 质的题目,于是把它变成了以下问题:对于任意一个正整数 n<=100000,如何求出{1, 2,..., n} 的满足上述约束条件的子集的个数(只需输出对 1,000,000,001 取模的结果),现在这个问题就 交给你了。

输入输出格式

输入格式:

 

只有一行,其中有一个正整数 n,30%的数据满足 n<=20。

 

输出格式:

 

仅包含一个正整数,表示{1, 2,..., n}有多少个满足上述约束条件 的子集。

 

输入输出样例

输入样例#1: 
4
输出样例#1:
8
 
【样例解释】 
 
有8 个集合满足要求,分别是空集,{1},{1,4},{2},{2,3},{3},{3,4},{4}。

发现可以将1-n分成若干组,其中每一组相互独立,第i组可以表示成形如
x 2x 4x 8x
3x 6x 12x 24x
9x 18x 36x 72x
这样随着行数递增列数单调不增的少一块的矩阵。
满足条件的方案就是矩阵中不能选出相邻的数。

因为每一组相互独立,所以可以将每一组的答案乘起来,得到最后的答案。
所以接下来看一下如何求一组的答案。

发现行数最多是10(3^11>10^5),所以可以把一列作为状态进行转移,
需要注意的是随着枚举的列编号增大,一列的行数是可能会减少的,这时候需要判定一下。

细节比较多(我的代码是把*3横着放所以一行一个状态),但是我的代码还算比较快的(bzoj rank 40/9??)。

/**************************************************************
    Problem: 2734
    User: JYYHH
    Language: C++
    Result: Accepted
    Time:28 ms
    Memory:1408 kb
****************************************************************/
 
#include<bits/stdc++.h>
#define ll long long
#define maxn 100005
#define ha 1000000001
using namespace std;
int ci[20],n,m;
int zt[1005],num=0;
bool v[maxn];
int f[20][250];
ll ans=1;
 
inline void init(){
    ci[0]=1;
    for(int i=1;i<=15;i++) ci[i]=ci[i-1]+ci[i-1];
}
 
inline ll solve(int pos){
    ll an=0; int l=0,h=0;
     
    for(int i=pos;i<=n;i<<=1)
        for(int j=i;j<=n;j*=3) v[j]=1;
    for(int i=pos;i<=n;i*=3,l++);
     
    num=0,f[0][1]=1;
    for(int i=0;i<ci[l];i++) if(!(i&(i<<1))) zt[++num]=i;
     
    h=1;
    for(;pos<=n;pos<<=1,h++){
        l=0;
        for(int j=pos;j<=n;j*=3,l++);
         
        for(int j=1;j<=num&&zt[j]<ci[l];j++)
            for(int k=1;k<=num;k++) if(!(zt[j]&zt[k])){
                f[h][j]+=f[h-1][k];
                if(f[h][j]>=ha) f[h][j]-=ha;
            }
    }
    h--;        
    for(int j=1;j<=num;j++){
        an+=f[h][j];
        if(an>=ha) an-=ha; 
    }
     
    for(int i=1;i<=h;i++)
        for(int j=1;j<=num;j++) f[i][j]=0;
     
    return an;
}
 
int main(){
    init();
    scanf("%d",&n);
     
    for(int i=1;i<=n;i++) if(!v[i]){
        ans=ans*solve(i);
        if(ans>=ha) ans-=ans/ha*ha;
    }
    printf("%lld\n",ans);
    return 0;
}

 

 

转载于:https://www.cnblogs.com/JYYHH/p/8313407.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值