033动态规划之状态压缩DP——算法备赛

状态压缩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 ?

kS-j 中的一个点,把 0~j 的路径分为 0-1-...-kk-(k+1)-...-j 以k为变量枚举 s~j 中所有点,找出最小路径

dp[s][j]=min{dp[s-j][k]+dis[k,j]}, k 属于 s-j

如何表示集合s

用一个二进制数表示SS中的每一为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;
}
相关推荐
ysa0510301 小时前
c++常用自带函数用法与注意
c++·笔记·算法
带多刺的玫瑰3 小时前
Leecode#4刷题之寻找两个正序数组的中位数
java·前端·算法
土司大王3 小时前
LeetCode hot100——除了自身以外数组的乘积
数据结构·算法·leetcode
IvanCodes3 小时前
RAG 实战教程(三):向量数据库检索算法,KNN、IVF、HNSW 与 Faiss 实战
人工智能·算法·agent
阿无,3 小时前
布隆过滤器
java·算法·哈希算法
徒慕风流3 小时前
激光雷达回波信号滤波与时刻鉴别算法:原理、代码实现与仿真验证
python·算法·激光雷达
这料鬼有毒3 小时前
二刷hot100-70.爬楼梯
算法
CIO_Alliance4 小时前
AI算法系列(2)| 决策树与随机森林XGBoost:企业AI化转型中的树模型选型逻辑
大数据·人工智能·算法·决策树·系统集成·企业cio联盟·企业级ai化转型
.道阻且长.5 小时前
10.LeetCode算法习题讲解--滑动窗口--最大连续为一的个数
算法·leetcode·职场和发展