动态规划--区间DP

区间DP

引入

区间DP主要用于解决涉及区间分割与合并的动态规划问题。

在这类问题中,我们往往会关注一段区间上的某种性质(方案数、组合数、最大值……)

而区间DP的状态转移则是枚举区间划分的最后一步,把区间一分为二,利用小的区间求得的值求得整个区间的值

由于大区间依赖小区间,所以长度都是从小遍历到大

一起到例题中看看吧~

例题
洛谷P1880 石子合并

题目描述

在一个圆形操场的四周摆放 N N N 堆石子,现要将石子有次序地合并成一堆,规定每次只能选相邻的 2 2 2 堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分。

试设计出一个算法,计算出将 N N N 堆石子合并成 1 1 1 堆的最小得分和最大得分。

输入

数据的第 1 1 1 行是正整数 N N N,表示有 N N N 堆石子。

2 2 2 行有 N N N 个整数,第 i i i 个整数 a i a_i ai 表示第 i i i 堆石子的个数。

1 ≤ N ≤ 100 1\leq N\leq 100 1N100 0 ≤ a i ≤ 20 0\leq a_i\leq 20 0ai20

输出

输出共 2 2 2 行,第 1 1 1 行为最小得分,第 2 2 2 行为最大得分。

样例输入 #1

4
4 5 9 4

样例输出 #1

43
54

分析

在这里插入图片描述

代码

//区间dp
#include<iostream>
#include<cstring>

using namespace std;

const int N = 210, INF = 0x3f3f3f3f;

int dp[N][N];
int n;
int a[N];

int main()
{
	cin >> n;

	for (int i = 1; i <= n; i++) cin >> a[i];
	for (int i = 1; i <= n; i++) a[i + n] = a[i];
	for (int i = 1; i <= 2 * n; i++) a[i] += a[i - 1];

	for (int len = 2; len <= n; len++)
		for (int i = 1; i + len - 1 < 2 * n; i++)
		{
			int j = i + len - 1;
			dp[i][j] = INF;
			for (int k = i; k + 1 <= j; k++)
				dp[i][j] = min(dp[i][j], dp[i][k] + dp[k + 1][j] + a[j] - a[i - 1]);
		}

	int mint = INF;
	for (int i = 1; i <= n; i++) mint = min(mint, dp[i][i + n - 1]);
	cout << mint << endl;

	for (int len = 2; len <= n; len++)
		for (int i = 1; i + len - 1 < 2 * n; i++)
		{
			int j = i + len - 1;
			dp[i][j] = 0;
			for (int k = i; k + 1 <= j; k++)
				dp[i][j] = max(dp[i][j], dp[i][k] + dp[k + 1][j] + a[j] - a[i - 1]);
		}

	int maxt = 0;
	for (int i = 1; i <= n; i++) maxt = max(maxt, dp[i][i + n - 1]);
	cout << maxt << endl;

	return 0;

}

:::

洛谷P4342 Polygon

题目描述

多边形是一个玩家在一个有 n n n 个顶点的多边形上的游戏,如图所示,其中 n = 4 n=4 n4 。每个顶点用整数标记,每个边用符号 + + +(加)或符号 ∗ * (乘积)标记。

第一步,删除其中一条边。随后每一步:

选择一条边连接的两个顶点 V 1 V_1 V1 V 2 V_2 V2 ,用边上的运算符计算 V 1 V_1 V1 V 2 V_2 V2 得到的结果来替换这两个顶点。

游戏结束时,只有一个顶点,没有多余的边。

如图所示,玩家先移除编号为3的边。之后,玩家选择计算编号为1的边,然后计算编号为4的边,最后,计算编号为2的边。结果是0。

(翻译者友情提示:这里每条边的运算符旁边的数字为边的编号,不拿来计算)

编写一个程序,给定一个多边形,计算最高可能的分数。

输入

输入描述一个有 n n n 个顶点的多边形,它包含两行。第一行是数字 n n n ,为总边数。

第二行描述这个多边形,一共有 2 n 2n 2n 个读入,每两个读入中第一个是字符,第二个是数字。

第一个字符为第一条边的计算符号( t t t 代表相加, x x x 代表相乘),第二个代表顶点上的数字。首尾相连。

3 ≤ n ≤ 50 3 \le n \le 50 3n50

对于任何一系列的操作,顶点数字都在 [ − 32768 , 32767 ] [-32768,32767] [32768,32767] 的范围内。

输出

第一行,输出最高的分数。在第二行,它必须写出所有可能的被清除后的边仍能得到最高得分的列表,必须严格递增。

样例输入 #1

4
t -7 t 4 x 2 x 5

样例输出 #1

33
1 2

:::

思路
在这里插入图片描述
在这里插入图片描述

代码

#include <iostream>
#include <vector>

using namespace std;

const int N = 110, INF = 32768;

int n;
char c[N];
int a[N];
int f[N][N], g[N][N];

int main()
{
    cin >> n;
    
    // 破环成链
    for (int i = 1; i <= n; i ++ )
    {
        cin >> c[i] >> a[i];
        c[i + n] = c[i];
        a[i + n] = a[i];
    }
    
    for (int len = 1; len <= n; len ++ )
        for (int i = 1; i + len - 1 <= 2 * n; i ++ )
        {
            int j = i + len - 1;
            
            // 只有一个数,不用操作
            if (len == 1)
            {
                f[i][j] = g[i][j] = a[i];
                continue;
            }
            
            // 枚举分界点
            f[i][j] = -INF, g[i][j] = INF; // 初始化
            for (int k = i; k < j; k ++ )
            {
                char op = c[k + 1]; // 取操作符
                int lm = g[i][k], lM = f[i][k], rm = g[k + 1][j], rM = f[k + 1][j];
                
                if (op == 't') f[i][j] = max(f[i][j], lM + rM), g[i][j] = min(g[i][j], lm + rm);
                else 
                {
                    int m = lm * rm, M = lm * rm;
                    
                    m = min(m, lm * rM);
                    m = min(m, lM * rm);
                    m = min(m, lM * rM);
                    
                    M = max(M, lm * rM);
                    M = max(M, lM * rm);
                    M = max(M, lM * rM);
                    
                    f[i][j] = max(f[i][j], M), g[i][j] = min(g[i][j], m);
                }
            }
            
        }
        
    int res = -INF; // 最大值
    vector<int> segs; // 方案
    for (int i = 1; i <= n; i ++ ) res = max(res, f[i][i + n - 1]);
    for (int i = 1; i <= n; i ++ )
        if (f[i][i + n - 1] == res)
            segs.push_back(i);

        
    cout << res << endl;
    for (auto i : segs)
        cout << i << " ";
    
    return 0;
}

洛谷P10956 金字塔

题目描述

虽然探索金字塔是极其老套的剧情,但是有一队探险家还是到了某金字塔脚下。

经过多年的研究,科学家对这座金字塔的内部结构已经有所了解。

首先,金字塔由若干房间组成,房间之间连有通道。

如果把房间看作节点,通道看作边的话,整个金字塔呈现一个有根树结构,节点的子树之间有序,金字塔有唯一的一个入口通向树根。

并且,每个房间的墙壁都涂有若干种颜色的一种。

探险队员打算进一步了解金字塔的结构,为此,他们使用了一种特殊设计的机器人。

这种机器人会从入口进入金字塔,之后对金字塔进行深度优先遍历。

机器人每进入一个房间(无论是第一次进入还是返回),都会记录这个房间的颜色。

最后,机器人会从入口退出金字塔。

显然,机器人会访问每个房间至少一次,并且穿越每条通道恰好两次(两个方向各一次), 然后,机器人会得到一个颜色序列。

但是,探险队员发现这个颜色序列并不能唯一确定金字塔的结构。

现在他们想请你帮助他们计算,对于一个给定的颜色序列,有多少种可能的结构会得到这个序列。

因为结果可能会非常大,你只需要输出答案对 1 0 9 10^9 109 取模之后的值。

输入

输入仅一行,包含一个字符串 S S S,长度不超过 300 300 300,表示机器人得到的颜色序列。

输出

输出一个整数表示答案。

样例输入 #1

ABABABA

样例输出 #1

5

思路

在这里插入图片描述

代码

#include <iostream>

using namespace std;

typedef long long LL;
const int mod = 1e9, N = 310;

int n;
string s;
int dp[N][N];

int main()
{
    cin >> s;
    int n = s.size();
    
    if (n % 2 == 0) puts("0"); // 无解
    else 
    {
        for (int len = 1; len <= n; len ++ )
            for (int i = 0; i + len - 1 < n; i ++ )
            {
                int j = i + len - 1;
                if (len == 1) dp[i][j] = 1; // 边界
                else if (s[i] == s[j]) // 两端相等才有解
                    // 找符合条件的
                    for (int k = i; k < j; k ++ )
                        if (s[k] == s[i])
                            dp[i][j] = (dp[i][j] + (LL)dp[i][k] * dp[k + 1][j - 1]) % mod;
            }
            
        cout << dp[0][n - 1] << endl;
    }
    
    return 0;
}
结语

区间DP其实思路基本大体都相似,基本上推出来子状态到当前状态的状态更新就都能做了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值