2020.02.13日常总结兼状压dp略讲(下)

一道状压好题——洛谷P3226 \color{green}{\text{一道状压好题——洛谷P3226}}

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

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

\color{blue}{【思路】:} 一道构造+状压dp的好题目。

我们可以构造这么一个矩阵,第 1 1 行第 1 1 个数是 1 1 ,其后,每行的第 j j 个元素是该行第 j 1 j-1 个数的 3 3 倍,每列的第 i i 个数是该列的第 i 1 i-1 个数的 2 2 倍。比如这样:

1  3  9  27  81...
2  6 18  54 162...
4 12 36 108 324...

然后,问题就变成了在这个矩阵中选数,且相邻的数不能同时选的方案数。

考虑到其行数和列数不是很大,所以我们可以用状压dp轻松解决。

记第 i i 行的数取或不取的状态用 0 , 1 0,1 表示, f i , j f_{i,j} 表示第 i i 行状态为 j j 时的答案。转移具体看代码。

注意因为从 1 1 开始构造矩阵不能把所有的数包含在内,需要把所有可能的矩阵构造出来,然后根据乘法原理,答案相乘即可。

\color{blue}{【代码】:}

const ll mod=1000000001;
const int N=100100,M=20;
ll line[M],f[2][1<<18],n;
bool chose[N],g[(1<<18)+5];
int a[M][M],lim[M];ll ans=1ll,t;
inline void initialization(){
	for(int i=0;i<(1<<18);i++)
		if ((i&(i<<1))==0)
			g[i]=true;
		else g[i]=false;
}//预处理g数组(g[i]:i是否为可行集合) 
inline void make_rectangle(int x){
	for(int i=1;i<12;i++){
		if (i==1) a[i][1]=x;
		else a[i][1]=a[i-1][1]*3;
		if (a[i][1]>n) break;
		t=i;chose[a[i][1]]=true;line[i]=1;
		for(int j=2;j<19;j++){
			a[i][j]=a[i][j-1]*2;
			if (a[i][j]>n) break;
			line[i]=j;chose[a[i][j]]=true;
		}
		lim[i]=(1<<line[i])-1;
	}
}//以x为首个数字构造我们需要的矩阵 
inline ll dp(){
	register int i,j,k;
	for(i=0;i<=lim[1];i++)
		f[1][i]=g[i];
	for(i=2;i<=t;i++)
		for(j=0;j<=lim[i];j++)
			if (g[j]){
				f[i&1][j]=0ll;//不能用memset 
				for(k=0;k<=lim[i-1];k++)
					if (g[k]&&((j&k)==0))
						f[i&1][j]=(f[i&1][j]+f[(i&1)^1][k])%mod;
			}
	register ll res=0ll;
	for(i=0;i<=lim[t];i++)
		if (g[i]) res=(res+f[t&1][i])%mod;
	return res;
}//状压dp求解子问题 
int main(){
	scanf("%lld",&n);
	initialization();
	for(int i=1;i<=n;i++)
		if (!chose[i]){
			make_rectangle(i);
			ans=(ans*dp())%mod;
		}
	printf("%lld",ans);
	return 0;
}

再谈状压 \color{green}{\text{再谈状压}}

  • 一般在数据范围不大的dp题中,我们会优先地考虑状压dp
  • 如果状态可以转化为一个东西取或不取,放或不放,就可以用二进制表示这个状态,然后把它放入状态的定义中,使用二进制运算符把这个问题变成状压dp求解的题目。
  • 二进制运算不是很好理解,在上一篇博客中我们讲了二进制运算符,所以大家可以根据状态的定义和二进制运算符的运算规矩画图理解。
发布了103 篇原创文章 · 获赞 4 · 访问量 6726

猜你喜欢

转载自blog.csdn.net/ZHUYINGYE_123456/article/details/104301341