返回顶部

状态压缩DP

我们需要选择5个数,对于每一个数,不选就为0,选就为1
那么对于5个数,我们就可以用5位的二进制数来表示当前的状态
这种将集合作为整数记录状态的一类算法叫做状态压缩DP

在状态压缩中,二位用二进制存储,所以有一些常用的操作:
(1)空集φ:0
(2)只含有第i个元素的集合{i}:1<<i
(3)含有全部n个元素的集合{0,1,...,n-1}:(1<<n)-1
(4)判断第i个元素是否属于集合S:if(S>>i&1)
(5)向集合S中加入第i个元素S∪{i}:S|1<<i
(6)从集合S中移出第i个元素S{i}:S&~(1<<i)
(7)集合S和T的并集S∪T:S|T
(8)集合S和T的交集S∩T:S&T
此外,想要将集合S={0,1,...,n-1}表示的所有状态枚举出来,可以写成:

for(int s=0;s<(1<<n);s++){
	//对子集的处理
}

例题

在这道题中,可以看到单词数目很少,容易想到枚举所有的单词连接情况,在判断最大长度,但情况太多(\(n!\)),会超时

所以用状态压缩DP
\(f[i][j]\)表示已经连接到了单词I,此时串的状态为S
状态用状态压缩,变成二进制
状态转移方程:
\(f[i][j+s]=max(f[i][j]+length[s])\)


最大状态为1111111111111111,转换成二进制就为65535,再加上1
状态压缩数组就是:

int f[16][65536];

常数数组:(只包含第i个单词)

const int only[16]={1,2,4,8,16,32,64,128,256,512,1024,2048,4096,8192,16384,32768};

边界条件说明:状态中只有一个单词的情况:\(f[i][only[i]]=len[i]\);


条件判断

if(only[i]&j){
//表示 “ 如果单词i在状态j中…”(注意:其实这里的i指的是第i+1个单词,下面一样,因为编号从0开始)
}

总代码:

#include<bits/stdc++.h>
using namespace std;
const int only[16]={1,2,4,8,16,32,64,128,256,512,1024,2048,4096,8192,16384,32768};
int n;
char s[16][102];
int f[16][65536];
int len[16];
int work(int i,int j){
	int j1,temp;
	if(f[i][j]>0) return f[i][j];
	j1=j-only[i];//从状态j中删去i
	for(int k=0;k<n;k++)
		if(i!=k&&s[i][len[i]-1]==s[k][0]&&(only[k]&j)){//可以接龙
			temp=work(k,j1)+len[i];
			f[i][j]=max(f[i][j],temp);
		}
	return f[i][j];
}
int main(){
	cin>>n;
	for(int i=0;i<n;i++){
		scanf("%s",s[i]);
		len[i]=strlen(s[i]);
	}
	for(int i=0;i<n;i++)
		f[i][only[i]]=len[i];
	int ans,temp;
	ans=0;
	for(int i=0;i<n;i++)\\从第i个单词开始连
		for(int j=1;j<(1<<n);j++)
			if((only[i]&j)){
				temp=work(i,j);
				ans=max(ans,temp);
			}
	cout<<ans<<endl;
	return 0;
}

谢谢观看
PS.
image

posted @ 2021-11-08 15:27  gyc#66ccff  阅读(52)  评论(0编辑  收藏  举报