BZOJ 2734 [HNOI2012] 集合选数 ( 状压DP )

版权声明:本人版权意识薄弱,版权声明什么的... https://blog.csdn.net/Ike940067893/article/details/87860784

这题的做法还挺神的…


题意
  • 《集合论与图论》这门课程有一道作业题,要求同学们求出 { 1 , 2 , 3 , 4 , 5 } \{1, 2, 3, 4, 5\} 的所有满足以下条件的子集:若 x x 在该子集中,则 2 x 2x 3 x 3x 不能在该子集中。同学们不喜欢这种具有枚举性 质的题目,于是把它变成了以下问题:对于任意一个正整数 N 100000 N≤100000 ,如何求出 { 1 , 2 , . . . , N } \{1, 2,..., N\} 的满足上述约束条件的子集的个数(只需输出对 1 , 000 , 000 , 001 1,000,000,001 取模的结果),现在这个问题就交给你了。
分析
  • 我们发现对于某个数 x x ,如果它选了就有两个数 2 x , 3 x 2x,3x 不能选,于是我们巧妙的构造一个矩阵如下:
    x 3 x 9 x . . . 2 x 6 x 18 x . . . 4 x 12 x 36 x . . . . . . . . . . . . . . . \begin{matrix} x&3x&9x&...\\ 2x&6x&18x&...\\ 4x&12x&36x&...\\ ...&...&...&...\\ \end{matrix}
  • 那么同一个矩阵中相邻的两个数就是不能选的,一行的选择只受上一行的制约。那么我们只要对于所有不是 2 2 的倍数且不是 3 3 的倍数的数 x x 都构造一个这样的矩阵,那么不同矩阵里的数一定不重复的且互相不制约,根据乘法原理把每个矩阵的方案数乘起来就是答案了
  • 因为要保证数的范围在 N N 内,那么矩阵的行数和列数都是 O ( l o g   n ) O(log\ n) 级别的,那么求方案就可以用简单的状压DP求了。 f ( i , j ) f(i,j) 表示当前到了第 i i 行状态为 j j 的可行方案数, j j 的每个位置上 1 1 表示选, 0 0 表示不选。
    f ( i , j ) = [ j ] j & k = 0 f ( i 1 , k ) \large f(i,j)=[j状态合法]*\sum_{j\&k=0} f(i-1,k)
  • 如果状态中选了大于 N N 的数或者是状态中同时选了相邻的两个数就不合法。此处 j & k = 0 j\&k=0 表示这一行与上一行没有同时选中相同的一列。
  • 注意一下不要每次都用 m e m s e t memset 清零滚动数组,实测用 m e m s e t memset 慢多了。下面的是用的 m e m s e t memset ,上面的使用的 f o r for 清零
    在这里插入图片描述
  • 注意模数
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\n", ans);
}

猜你喜欢

转载自blog.csdn.net/Ike940067893/article/details/87860784