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 ?

设 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;
}
相关推荐
学电子她就能回来吗5 小时前
LeetCode Hot 100|普通数组专题学习笔记(零基础版)
笔记·算法·leetcode
用户老李5 小时前
反向传播计算梯度举例
算法
niyongsheng5 小时前
多智能体连续时间内路径规划:AA-SIPP(m) 的 Rust 实现与算法
算法·rust·机器人
302wanger6 小时前
DeepSeek-V4.1-Flash-1017 免费用到1024,速来
算法
lisw056 小时前
微分与积分的本质区别解析
算法·数学建模·极限编程
xys_6787 小时前
深挖数据结构:树和二叉树(上)
数据结构·算法
Interview Aid1127 小时前
Google SWE Intern VO 面经:两轮 Coding,Follow-up 考察 BFS 状态
算法
琳达爱分享8 小时前
从单机令牌桶到集群限流:一次配额超发的复盘
后端·算法
range8 小时前
带TTL的LRU缓存的实现-字节手撕-26/10/19
算法
糖果豆豆8 小时前
n log n 到底从哪来?排序下界、2-SAT 与 3-SAT、停机问题,卡的是同一件事
算法·架构