状态压缩DP
状态压缩是DP的一个技巧,一般应用于集合问题,当DP的状态是集合时,把集合的组合用一个二进制数表示,这个二进制数的0/1组合表示集合中元素的有无。
状态压缩DP的背景是集合状态,且集合一般可用二进制数表示,所以又称"集合DP"。
每个元音包含偶数次的最长子串
问题描述
给你一个字符串 s ,请你返回满足以下条件的最长子字符串的长度:每个元音字母,即'a','e','i','o','u',在子字符串中都恰好出现了偶数次。
思路分析
将字符串中 5 个元音字母出现次数的奇偶性定义为一种状态,一共有 32(25)种状态,可使用一个整数代表该状态,第0位表示a的奇偶性,第1位表示e出现的奇偶性...依次类推。仅有状态为0的字符串符合题目要求。
定义状态的目的是遍历s子串的右边界 时不用再二层遍历寻找左边界。如果子串[0,i]状态为st1,与子串[0,j]的状态为st2,那子串[i+1,j]的状态就是st1^st2。只有当st1==st2时,子串[i+1,j]的状态才是0。两者是充分必要的关系。
对于每次枚举的右边界,找到最小的与其状态相同的左边界。统计的历史最大值就是答案。
代码
cpp
int findTheLongestSubstring(string s) {
vector<int> pre(32,INT_MAX); //pre[i] 存储状态为i的最小左边界,初始时为INT_MIN
pre[0]=-1;
const int N=s.size();
int cur=0;
int ans=0;
for(int i=0;i<N;++i){
switch(s[i]){
case 'a':cur^=1;break;
case 'e':cur^=2;break;
case 'i':cur^=4;break;
case 'o':cur^=8;break;
case 'u':cur^=16;break;
default:break;
}
if(pre[cur]==INT_MAX) pre[cur]=i; //未初始化,现在初始化
else ans=max(ans,i-pre[cur]);
}
return ans;
}
Hamilton(旅行商)问题
问题描述
给定一个有权无向图,包括 n 个点,标记为 0~n-1,以及连接 n 个点的边,求从起点 0到终点 n-1的最短路径。要求必须经过所有点,且只经过一次。1<=n<=20。
输入:第一行输入 n,接下来 n 行,每行输入 n 个整数,其中第 i 行第 j 个整数表示点 i 到点 j 的边的距离,记为 a[i,j]。a[x,x]=0,a[x,y]>=0
输出最短距离。
思路
定义dp,设 s 为图的一个子图,用dp[s][j]表示集合 s 内的最短 Hamilton路径,即从起点0出发经过S所有点到 j 的最短路径。
如何从 S-j (集合s中去掉j)递推到S ?
设 k 为 S-j 中的一个点,把 0~j 的路径分为 0-1-...-k和 k-(k+1)-...-j 以k为变量枚举 s~j 中所有点,找出最小路径
dp[s][j]=min{dp[s-j][k]+dis[k,j]}, k 属于 s-j。
如何表示集合s
用一个二进制数表示S,S中的每一为0/1表示图中点的有无。本题n最多20,用int数据(32位)够用。
代码
cpp
int n,dp[1<<20][21];
int dist[21][21];
int main(){
memset(dp,0x7f,sizeof(dp)); //初始化最大值
//用容器可表示为 vector<vector<int>>dp(1<<20,vector<int>(21,INT_MAX));
cin>>n;
for(int i=0;i<n;i++){ //输入图
for(int j=0;j<n;j++){
cin>>dist[i][j];
}
}
dp[0][0]=0;
for(int s=1;s<(1<<n);i++){
for(int j=0;j<n;j++){
if((s>>j)&1) //判断第j位是否为1 即判断子图s中是否有j (1) 没有直接下一轮循环。
for(int k=0;k<n;k++){ //枚举每个k 取最小值
int st=s^(1<<j)//s^(1<<j) 用于将s中第j为置0,此时st为不含j的子图
if(st>>k&1) //if(((s>>j)&1)&&((s^(1<<j))>>k&1)) 把(1)合并进来这样写也行,但效率低些
dp[s][j]=min(dp[s][j],dp[st][k]+dist[k][j]);
}
}
}
cout<<dp[(1<<n)-1][n-1];
return 0;
}