zoukankan      html  css  js  c++  java
  • [HNOI2012]集合选数

    洛咕

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

    分析:构建如下的矩阵:

    (egin{matrix} 1 & 3 & 9 & 27 & 81 &...\ 2 & 6 & 18 & 54 & 162 & ...\ 4 & 12 & 36 & 108 & 324 &...\ 8 & 24 & 72 & 216 & 648 &...\ ... &... &... &... &...end{matrix})

    容易发现,矩阵里面不能取相邻的两个数,这样就可以状压(DP)了.设(f[i][j])表示第i行取数的状态为j时的方案数.(f[i][j]+=f[i-1][k])(当(j)&(k)=0且(j)&(j<<1=0),(k)&(k<<1=0):前式表示相邻两行中没有相邻的数都被取到,后式表示同一行中相邻两列没有相邻的数被取到.)

    因为(n<=100000),(2^{16}=65536,2^{17}=131072),所以最多只有17行,同理,(3^{10}=59049),(3^{11}=177147),所以最多只有11列.

    然后因为一个矩阵不可能包括(1)(n)之内的所有数,所以我们要记录每个数是否在矩阵中出现过,若没出现过就以该数为矩阵的第一行第一列上的元素来构建矩阵,并状压(DP)计算方案.

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    #include<cmath>
    #include<queue>
    #include<map>
    #include<set>
    #define ll long long
    using namespace std;
    inline int read(){
        int x=0,o=1;char ch=getchar();
        while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
        if(ch=='-')o=-1,ch=getchar();
        while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
        return x*o;
    }
    const int mod=1000000001;
    const int N=20;
    const int M=100005;
    int Base[N],limit[N],visit[M],jz[N][N];
    int n;ll ans=1,f[N][M];
    inline int solve(int x){
    	jz[1][1]=x;//矩阵的第一行第一列的元素
    	for(int i=2;i<=17;++i){
    		if(jz[i-1][1]*2<=n)jz[i][1]=jz[i-1][1]*2;
    		else jz[i][1]=n+1;
    	}//构建矩阵的第一列,限制数字最大为n+1
    	for(int i=1;i<=17;++i)
    		for(int j=2;j<=11;++j){
    			if(jz[i][j-1]*3<=n)jz[i][j]=jz[i][j-1]*3;
    			else jz[i][j]=n+1;
    		}//根据每一行第一列上的数,构建矩阵的每一行
    	for(int i=1;i<=17;++i){
    		limit[i]=0;//状压枚举每一行状态的最大限制
    		for(int j=1;j<=11;++j){
    			if(jz[i][j]<=n){
    				limit[i]+=Base[j-1];
    				visit[jz[i][j]]=1;//标记该数在矩阵中出现过
    			}
    			else break;//之后的数也都会比n大
    		}
    	}
    	for(int i=1;i<=17;++i)
    		for(int j=0;j<=limit[i];++j)
    			f[i][j]=0;
    	f[0][0]=1;//初始化
    	for(int i=1;i<=17;++i)
    		for(int j=0;j<=limit[i];++j){
    			if(j&(j<<1))continue;//同一行相邻两数不能都取
    			for(int k=0;k<=limit[i-1];++k){
    				if((j&k)||(k&(k<<1)))continue;
    				f[i][j]=(f[i][j]+f[i-1][k])%mod;//累加方案数
    			}
    		}
    	ll cnt=0;
    	for(int j=0;j<=limit[17];++j)cnt+=f[17][j];//最后一行每个状态的方案数之和就是此次的贡献
    	return cnt;
    }
    int main(){
    	n=read();
    	Base[0]=1;for(int i=1;i<=12;++i)Base[i]=Base[i-1]<<1;//预处理出2的次幂
    	for(int i=1;i<=n;++i)//对于每一个没有出现过的数计算共吸纳
        	if(!visit[i])ans=(1ll*ans*solve(i))%mod;
    	printf("%lld
    ",ans);
        return 0;
    }
    
    
  • 相关阅读:
    [MongoDB]
    [solr]
    数据结构-二叉树
    vue-学习笔记-Class 与 Style 绑定
    vue-学习笔记-计算属性和侦听器(computed和watch)
    lodash的debounce函数
    vue-学习笔记-模板语法
    vue-学习笔记-Vue 实例
    vue-介绍章节
    工具网站推荐-jsfiddle,一款在线写代码的网站
  • 原文地址:https://www.cnblogs.com/PPXppx/p/11589024.html
Copyright © 2011-2022 走看看