个人学习笔记平台,欢迎大家交流

红叶~

Be humble, communicate clearly, and respect others.

区间DP

区间DP

区间DP常用模板
所谓区间DP,指在一段区间上进行动态规划,一般做法是由长度较小的区间往长度较大的区间进行递推,最终得到整个区间的答案,边界就是长度为1和2的区间

所有的区间DP问题,有两种写法
1、第一维都是枚举区间长度,第二维枚举起点,先求出小区间的属性值,进而扩展到大区间
核心思想:小区间 ---> 大区间

for (int i = 1; i <= n; i++) {
    dp[i][i] = 初始值
}
for (int len = 2; len <= n; len++)           //区间长度
    for (int i = 1; i + len - 1 <= n; i++) { //枚举起点
        int j = i + len - 1;                 //区间终点
        for (int k = i; k < j; k++) {        //枚举分割点,构造状态转移方程
            dp[i][j] = max(dp[i][j], dp[i][k] + dp[k + 1][j] + w[i][j]);
        }
    }

2、第一维从最尾部开始枚举起点,第二维枚举终点(从短到长)

for(int i = n;i >= 1;i--) { // 起点从尾部开始
  dp[i][i] = 初始值;
  for(int j = i + 1;j <= n;j++) { // 终点
    f[i][j] = max(.......)
  }
}

1、区间DP复习

2、石子合并

3、PKUpoj3280

题目描述:给定一个字符串,通过增加或删除相关字母形成回文串,每一个不同的字母增加和删除操作需要的花费不同,求形成回文串所需的最小代价

思路

image-20220216113938675

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 2010;
char s[N];
int n, m;
int cost[30];
int dp[N][N];//dp[i][j] 存储从 i 到 j 这一段转换成回文子序列需要的最小花费

int main() {
	ios::sync_with_stdio(false);//将stdio解除绑定,使得cin与scanf效率相差无几
	int a, b;
	char t;
	while (cin >> n >> m) {
		cin >> s + 1;
		while (n--) {
			cin >> t >> a >> b;
			cost[t - 'a'] = min(a, b);
		}
//		for (int i = m; i >= 1; i--) {
//			dp[i][i] = 0;
//			for (int j = i + 1; j <= m; j++) {
//				if (s[i] == s[j]) {
//					if (j - i <= 2)
//						dp[i][j] = 0;
//					else
//						dp[i][j] = dp[i + 1][j - 1];
//				} else
//					dp[i][j] = min(dp[i + 1][j] + cost[s[i] - 'a'], dp[i][j - 1] + cost[s[j] - 'a']);
//			}
//		}
		for (int i = 1; i <= m; i++)
			dp[i][i] = 0;
		for (int len = 2; len <= m; len++) // 长度,从短到长
			for (int i = 1; i + len - 1 <= m; i++) { // 起点
				int j = i + len - 1; // 终点
				if (s[i] == s[j]) {
					if (j - i <= 2)
						dp[i][j] = 0;
					else
						dp[i][j] = dp[i + 1][j - 1];
				} else {
					if (j - i <= 1)
						dp[i][j] = min(cost[s[i] - 'a'], cost[s[j] - 'a']);
					else
						dp[i][j] = min(dp[i + 1][j] + cost[s[i] - 'a'], dp[i][j - 1] + cost[s[j] - 'a']);
				}
			}

		printf("%d\n", dp[1][m]);
	}
	return 0;
}
关于起点从尾部开始递推的写法的原因
`dp[i][j] = min(dp[i+1][j], dp[i][j-1])`
由于`dp[i][i]`起点是i,而它是由起点 i + 1比较得来的,所以可以从尾部开始,那么第二层循环就是遍历长度(防止越界)

4、括号配对
题意:给出一串括号,要求输出添加括号最少,并使所有括号均有匹配的串
思路:
状态表示:dp[l][r]表示向字符串中的i~j变成匹配的串的最少添加数量
状态计算:1、l与r匹配,dp[l][r] = dp[l+1][r-1],举个例子比如:[]([],如果直接按照前面这样计算dp[l][r],]([的结果为3,但是实际答案却是1。可以先匹配[] + ([],也就是f[1,2]+f[3,5]。
遍历该区间的子区间,看是否有匹配的括号串,从而减少答案。dp[l][r] = dp[l][k] + dp[k+1][r]

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

const int N = 110, inf = 0x3f3f3f3f;

char s[N];
int f[N][N]; // f[i][j]代表将i~j个字符变成GBE的所有方案的集合的最小值

bool check(int a, int b)
{
    if (s[a] == '[' && s[b] == ']' || s[a] == '(' && s[b] ==')') return true;
    return false;
}

int main()
{
    scanf("%s", s + 1);
    int n = strlen(s + 1);

    for (int len = 1; len <= n; len++)
        for (int l = 1; l + len - 1 <= n; l++)
        {
            int r = l + len - 1;
            if (len == 1) f[l][r] = 1;
            else
            {
                f[l][r] = inf;
                if (check(l, r)) f[l][r] = min(f[l][r], f[l + 1][r - 1]);
                for (int k = l; k < r; k++)
                    f[l][r] = min(f[l][r], f[l][k] + f[k + 1][r]);
            }
        }

    cout << f[1][n] << endl;

    return 0;
}

附:记录输出最终的答案
pos[i][j] = k表示括号串i-j中以第k个位置分开使得两部分匹配
pos[i][j] == -1表示i和j两端点匹配

最长对称子串
暴力DP

A[l][r]为字符串 str[l:r]的最长对称子串

对于第 l 位与第 r 位之间的字符串str[l:r],如果其是对称子串的话,则满足str[l] == str[r],且str[l+1: r-1]为对称子串

初始条件

A[i][j] == 1 - > i == j

#include<isotream>
using namespace std;
vector<vector<int>> A(n,vector<int>(n,0));
int main()
{
    string str = "";
    getline(cin,str);
    int n = str.size();
    for(int i = 0;i < n;i++)
        A[i][i] = 1;
   	int Max = 1;
    for(int len = 1;len < n;len++) // 枚举子串长度
    {
        for(int l = 0;l + len< n;l++)
        {
            int r = l + len;
            if(str[l] == str[r])
            {
                if(A[l+1][r-1] == r - l -1)// 如果该区间的最长对称子串等于总长度
                {
                    A[l][r] = A[l+1][r-1] + 2;
                    if(A[l][r] > Max)
                        Max = A[l][r];
                }
            }
        }
    }    
}

动态规划解法

#include<iostream>
#include<cstring>
using namespace std;
const int N = 1010;
bool dp[N][N]; // dp[i][j]表示的是(i,j)字符串是否是回文串
string s;
int main() {
    getline(cin, s);
    int n = s.length();
    int maxLen = 1;
    for(int i = 0; i < n;i++)   dp[i][i] = true; // 单个字符也是回文串
    for(int L = 2; L <= n; L++) { // 遍历长度
        for(int i = 0 ;i < n;i++) { // 遍历起点
            int j = i + L - 1; // 终点
            if(j >= n)  break;
            if(s[i] != s[j]) dp[i][j] = false;
            else {
                if(j - i <= 2)  dp[i][j] = true;
                else    dp[i][j] = dp[i+1][j-1];
            }
            if(dp[i][j] && j - i + 1 > maxLen)  maxLen = j - i + 1;
        }
    }
    cout <<maxLen << endl;
    return 0;
}
posted @ 2022-02-16 11:49  红叶~  阅读(40)  评论(0编辑  收藏  举报