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

版权声明:https://blog.csdn.net/huashuimu2003 https://blog.csdn.net/huashuimu2003/article/details/89533578

title

BZOJ 2734
LUOGU 3226
Description

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

Input

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

Output

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

Sample Input

4

Sample Output

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

Source

day2

analysis

思路非常妙啊!
来看一个矩形:

1   2   4   8   16  .....
3   6   12  24  48  .....
9   18  36  72  144 .....
.   .   .   .   .   .....
.   .   .   .   .   .....

然后,就变成了,有若干个这样的矩阵,求出选不相邻的数的选法数

那么就是状压DP的常规套路了,

因为 n 100000 n \le100000 ,所以该矩阵的行和列一定都很小,最多大概是在 12 12

所以我们可以考虑在这个矩阵上状压每一行,然后统计一下该矩阵可取的方案数即可。

即设 f [ k ] [ s ] f[k][s] 表示矩形第 k k 行状态为 s s 时的集合数。

可以发现这样的矩阵有很多,因为 5 5 7 7 没有出现在这个矩阵中。

所以在找完 1 1 为左上角的该类型矩阵后,我们只需要寻找 1 N 1\sim N 中的下一个没有出现在矩阵中的元素充当 左上角 ,再次计算即可。

由于不同矩阵中的元素互不影响,所以我们把所有可能的矩阵的方案数利用 乘法原理 乘在一起即可。

code

#include<bits/stdc++.h>
using namespace std;
const int mod=1e9+1;
const int maxn=20,maxs=1<<12;

template<typename T>inline void read(T &x)
{
	x=0;
	T f=1, ch=getchar();
	while (!isdigit(ch) && ch^'-') ch=getchar();
	if (ch=='-') f=-1, ch=getchar();
	while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
	x*=f;
}

int N,f[maxn][maxs],tot[maxn];
bool p[maxs];
inline int solve(int x)
{
	memset(tot,0,sizeof(tot));
	int n=1;
	for (int s=x; s<=N; ++n, s<<=1)
		for (int k=s; k<=N; k*=3) ++tot[n];
	--n;

	memset(f,0,sizeof(f));
	f[0][0]=1;
	for (int i=1; i<=n; ++i)
		for (int j=0; j<= (1<<tot[i-1]) -1; ++j) if (p[j])
			for (int k=0; k<= (1<<tot[i]) -1; ++k) if (p[k])
				if (!(j&k))
					f[i][k]=(f[i][k]+f[i-1][j])%mod;

	int res=0;
    for (int i=0; i<= (1<<tot[n]) -1; ++i) res=(res+f[n][i])%mod;
    return res;
}

int main()
{
	read(N);
	for (int i=0; i<(1<<12); ++i)
		if (!(i&(i>>1)) && !(i&(i<<1))) p[i]=true;
	int ans=1;
	for (int i=1; i<=N; ++i)
		if ((i%2) && (i%3)) ans=(ans*1ll*solve(i))%mod;

	printf("%d\n",ans);
	return 0;
}

summary

思路是真的很棒,刚开始看到此题,想着怎么把这个集合数量转化为一个可以压缩状态的东西?

然后暴毙了。。

看过yybwzq b l o g blog 后,猛然如大梦初醒,这个方法是真的挺难想到的, H N O I HNOI 是真的强!

所以说状压DP,一定是能将题目中的关系转化成棋盘类问题的或者用了他的思想,关键就是这个转化了。

接下来,就要训练这种思维能力了。

猜你喜欢

转载自blog.csdn.net/huashuimu2003/article/details/89533578