zoukankan      html  css  js  c++  java
  • BZOJ 2734 [HNOI2012] 集合选数 ( 状压DP )

    这题的做法还挺神的…


    题意
    • 《集合论与图论》这门课程有一道作业题,要求同学们求出 {1,2,3,4,5}{1, 2, 3, 4, 5} 的所有满足以下条件的子集:若 xx 在该子集中,则 2x2x3x3x 不能在该子集中。同学们不喜欢这种具有枚举性 质的题目,于是把它变成了以下问题:对于任意一个正整数 N100000N≤100000,如何求出 {1,2,...,N}{1, 2,..., N} 的满足上述约束条件的子集的个数(只需输出对 1,000,000,0011,000,000,001 取模的结果),现在这个问题就交给你了。
    分析
    • 我们发现对于某个数 xx,如果它选了就有两个数 2x,3x2x,3x不能选,于是我们巧妙的构造一个矩阵如下:
      x3x9x...2x6x18x...4x12x36x............... egin{matrix} x&3x&9x&...\ 2x&6x&18x&...\ 4x&12x&36x&...\ ...&...&...&...\ end{matrix}
    • 那么同一个矩阵中相邻的两个数就是不能选的,一行的选择只受上一行的制约。那么我们只要对于所有不是22的倍数且不是33的倍数的数 xx都构造一个这样的矩阵,那么不同矩阵里的数一定不重复的且互相不制约,根据乘法原理把每个矩阵的方案数乘起来就是答案了
    • 因为要保证数的范围在NN内,那么矩阵的行数和列数都是O(log n)O(log n)级别的,那么求方案就可以用简单的状压DP求了。f(i,j)f(i,j)表示当前到了第ii行状态为jj的可行方案数,jj的每个位置上11表示选,00表示不选。
      f(i,j)=[j]j&k=0f(i1,k)large f(i,j)=[j状态合法]*sum_{j&k=0} f(i-1,k)
    • 如果状态中选了大于NN的数或者是状态中同时选了相邻的两个数就不合法。此处 j&k=0j&k=0 表示这一行与上一行没有同时选中相同的一列。
    • 注意一下不要每次都用 memsetmemset 清零滚动数组,实测用 memsetmemset 慢多了。下面的是用的memsetmemset,上面的使用的forfor清零
      在这里插入图片描述
    • 注意模数
    AC代码
    #include <bits/stdc++.h>
    using namespace std;
    const int MAXN = 100005;
    const int mod = 1e9 + 1;
    int N;
    
    
    int arr[25][15], st[1<<15], tot, f[2][1<<15];
    inline int solve(int s) {
    	int n = 1, m = 1;
    	memset(arr, -1, sizeof arr);
    	arr[0][0] = s;
    	while(arr[0][m-1] * 3 <= N) arr[0][m] = arr[0][m-1] * 3, ++m;
    	while(arr[n-1][0] * 2 <= N) arr[n][0] = arr[n-1][0] * 2, ++n;
    	for(int i = 1; i < n; ++i)
    		for(int j = 1; j < m; ++j)
    			if(~arr[i-1][j] && arr[i-1][j]*2 <= N) arr[i][j] = arr[i-1][j] * 2;
    	tot = 0;
    	for(int state = 0; state < (1<<m); ++state) { //预处理出合法状态
    		bool flg = 1;
    		for(int i = 1; i < m && flg; ++i)
    			if((state>>i)&1 && (state>>(i-1))&1) flg = 0;
    		if(flg) st[tot++] = state;
    	}
    	int now = 0;
    	for(int j = 0; j < (1<<m); ++j) f[now][j] = 0;
    	f[now][0] = 1;
    	for(int i = 0; i < n; ++i) {
    		now ^= 1;
    		for(int j = 0; j < (1<<m); ++j) f[now][j] = 0; //for清零
    		for(int pre = 0, state; pre < tot; ++pre) if(f[now^1][state=st[pre]])
    			for(int nxt = 0, news; nxt < tot; ++nxt) if(!(state&(news=st[nxt]))) {
    				bool flg = 1;
    				for(int j = 0; j < m && flg; ++j)
    					if(arr[i][j] == -1 && (news>>j)&1) flg = 0; //-1表示这个数大于N
    				if(flg) f[now][news] = (f[now][news] + f[now^1][state]) % mod;
    			}
    	}
    	int res = 0;
    	for(int i = 0; i < tot; ++i)
    		res = (res + f[now][st[i]]) % mod;
    	return res;
    }
    
    int main () {
    	scanf("%d", &N);
    	int ans = 1;
    	for(int i = 1; i <= N; ++i) if((i%2) && (i%3))
    		ans = 1ll * ans * solve(i) % mod;
    	printf("%d
    ", ans);
    }
    
  • 相关阅读:
    iOS9 HTTP 不能正常使用的解决办法
    IOS UIWebView的一些用法总结
    顺序查找
    循环队列
    队列的链式存储实现
    栈的链式存储实现
    顺序表的实现
    MessageBox函数
    二分法查找
    冒泡排序
  • 原文地址:https://www.cnblogs.com/Orz-IE/p/12039436.html
Copyright © 2011-2022 走看看