算法学习系列(三十七):复杂DP

本文详细讲解了区间DP在石子合并中的应用,树形DP处理没有上司舞会的策略,以及记忆化搜索在滑雪轨迹问题中的解决方案。
摘要由CSDN通过智能技术生成

引言

本章的DP内容主要牵涉到区间DP、树形DP、和记忆化搜索,DP问题没有一个好的模板或者套路,只能说是看题,这种题你会了也就会了,不会也就不会,所以说还是以题目为主来介绍这些DP问题。


一、石子合并

标签:区间DP

思路:这道题跟合并果子超级胶水的题很像,有兴趣的可以看看,区别在于合并果子是合并没有限制,该题是必须是相邻的才能合并, 而超级胶水是必须相邻,但代价是两个之积,所以得要区分开来。该题用的是 D P DP DP ,其实就是相当于优化了的暴搜,核心就是从每个能够合并的区间里去合并,然后找到最小值, f [ i ] [ j ] f[i][j] f[i][j] 代表下标从 i ∼ j i\sim j ij 的选法方案数中最优代价和 f [ i ] [ j ] = m i n ( f [ i ] [ j ] , f [ i ] [ k ] + f [ k + 1 ] [ j ] + s [ r ] − s [ l − 1 ] ) f[i][j] = min(f[i][j],f[i][k] + f[k+1][j] + s[r] - s[l-1]) f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+s[r]s[l1])

题目描述:

设有 N 堆石子排成一排,其编号为 1,2,3,…,N。

每堆石子有一定的质量,可以用一个整数来描述,现在要将这 N 堆石子合并成为一堆。

每次只能合并相邻的两堆,合并的代价为这两堆石子的质量之和,合并后与这两堆石子相邻的石子将和新堆相邻,合并时由于
选择的顺序不同,合并的总代价也不相同。

例如有 4 堆石子分别为 1 3 5 2, 我们可以先合并 1、2 堆,代价为 4,得到 4 5 2, 又合并 1、2 堆,代价为 9,得到 9 2 ,
再合并得到 11,总代价为 4+9+11=24;
如果第二步是先合并 2、3 堆,则代价为 7,得到 4 7,最后一次合并代价为 11,总代价为 4+7+11=22。

问题是:找出一种合理的方法,使总的代价最小,输出最小代价。

输入格式
第一行一个数 N 表示石子的堆数 N。
第二行 N个数,表示每堆石子的质量(均不超过 1000)。

输出格式
输出一个整数,表示最小代价。

数据范围
1≤N≤300
输入样例:
4
1 3 5 2
输出样例:
22

示例代码:

#include <cstdio>
#include <iostream>

using namespace std;

const int N = 310;

int n;
int a[N], s[N];
int f[N][N];  // f[i][j]代表从下标为i-j的石子中合并的最小代价

int main()
{
    scanf("%d", &n);
    for(int i = 1; i <= n; ++i) scanf("%d", &a[i]);
    for(int i = 1; i <= n; ++i) s[i] = s[i-1] + a[i];  // 处理前缀和
    
    for(int len = 2; len <= n; ++len)
    {
        for(int i = 1; i + len - 1 <= n; ++i)
        {
            int l = i, r = i + len - 1;
            f[l][r] = 2e9;
            for(int k = l; k < r; ++k) f[l][r] = min(f[l][r], f[l][k] + f[k+1][r] + s[r] - s[l-1]);
        }
    }
    
    printf("%d\n", f[1][n]);
    
    return 0;
}

二、没有上司的舞会

标签:树形DP

思路:树就是一个无向无环图,需要借助邻接表,忘了的可以看树与图的DFS与BFS。整体来说就是一个递归然后回溯的过程,首先从根结点递归进去,然后到根,然后从根上来根据推导公式再逐步往上推,最后就找到最大值了。需要的公式为 f [ u ] [ 0 ] f[u][0] f[u][0] 代表以 u u u 为根结点,并且不选 u u u 的子树最大值, f [ u ] [ 1 ] f[u][1] f[u][1] 代表以 u u u 为根节点选 u u u 的字树最大值,推导公式为: f [ u ] [ 0 ] = ∑ m a x ( f [ j ] [ 0 ] , f [ j ] [ 1 ] ) , f[u][0] = \sum max(f[j][0], f[j][1]), f[u][0]=max(f[j][0],f[j][1]), f [ u ] [ 1 ] = ∑ f [ j ] [ 0 ] , j 为 u 的孩子 f[u][1] = \sum f[j][0],j为u的孩子 f[u][1]=f[j][0],ju的孩子

题目描述:

Ural 大学有 N 名职员,编号为 1∼N.

他们的关系就像一棵以校长为根的树,父节点就是子节点的直接上司。

每个职员有一个快乐指数,用整数 Hi 给出,其中 1≤i≤N。

现在要召开一场周年庆宴会,不过,没有职员愿意和直接上司一起参会。

在满足这个条件的前提下,主办方希望邀请一部分职员参会,使得所有参会职员的快乐指数总和最大,求这个最大值。

输入格式
第一行一个整数 N。
接下来 N 行,第 i 行表示 i 号职员的快乐指数 Hi。
接下来 N−1 行,每行输入一对整数 L,K,表示 K 是 L 的直接上司。(注意一下,后一个数是前一个数的父节点,不要搞反)。

输出格式
输出最大的快乐指数。

数据范围
1≤N≤6000,−128≤Hi≤127
输入样例:
7
1
1
1
1
1
1
1
1 3
2 3
6 4
7 4
4 5
3 5
输出样例:
5

示例代码:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 6010;

int n;
int happy[N];
int h[N], e[N], ne[N], idx;
int f[N][2];  // f[u][0]代表以u为根结点不选u的最大值  f[u][1]代表以u为根结点不选u的最大值
bool has_father[N];

void add(int a, int b)
{
    e[idx] = b; ne[idx] = h[a], h[a] = idx++;
}

void dfs(int u)
{
    f[u][1] = happy[u];
    
    for(int i = h[u]; i != -1; i = ne[i])
    {
        int j = e[i];
        
        dfs(j);
        
        f[u][0] += max(f[j][0], f[j][1]);
        f[u][1] += f[j][0];
    }
}

int main()
{
    memset(h, -1, sizeof h);  // 初始化h数组
    
    scanf("%d", &n);
    for(int i = 1; i <= n; ++i) scanf("%d", &happy[i]);
    
    for(int i = 0; i < n - 1; ++i)
    {
        int a, b;
        scanf("%d%d", &a, &b);
        has_father[a] = true;
        add(b,a);
    }
    
    int root = 1;
    while(has_father[root]) root++;
    
    dfs(root);
    
    printf("%d\n", max(f[root][0], f[root][1]));
    
    return 0;
}

三、滑雪

标签:记忆化搜索

思路:这道题就是一个 D F S DFS DFS 递归,然后根据条件不断地更新 f [ i ] [ j ] f[i][j] f[i][j] 的值,直至更新所有的点,最后找出最大值即可。

题目描述:

给定一个 R 行 C 列的矩阵,表示一个矩形网格滑雪场。

矩阵中第 i 行第 j 列的点表示滑雪场的第 i 行第 j 列区域的高度。

一个人从滑雪场中的某个区域内出发,每次可以向上下左右任意一个方向滑动一个单位距离。

当然,一个人能够滑动到某相邻区域的前提是该区域的高度低于自己目前所在区域的高度。

下面给出一个矩阵作为例子:

 1  2  3  4 5

16 17 18 19 6

15 24 25 20 7

14 23 22 21 8

13 12 11 10 9
在给定矩阵中,一条可行的滑行轨迹为 24−17−2−1。

在给定矩阵中,最长的滑行轨迹为 25−24−23−…−3−2−1,沿途共经过 25 个区域。

现在给定你一个二维矩阵表示滑雪场各区域的高度,请你找出在该滑雪场中能够完成的最长滑雪轨迹,并输出其长度(可经过最大区域数)。

输入格式
第一行包含两个整数 R 和 C。
接下来 R 行,每行包含 C 个整数,表示完整的二维矩阵。

输出格式
输出一个整数,表示可完成的最长滑雪长度。

数据范围
1≤R,C≤300,0≤矩阵中整数≤10000
输入样例:
5 5
1 2 3 4 5
16 17 18 19 6
15 24 25 20 7
14 23 22 21 8
13 12 11 10 9
输出样例:
25

示例代码:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 310;

int n, m;
int h[N][N];
int f[N][N];

int dir[4][2] = {0,1,0,-1,1,0,-1,0};

int dp(int x, int y)
{
    if (f[x][y] != -1) return f[x][y];

    f[x][y] = 1;
    for (int i = 0; i < 4; i ++ )
    {
        int a = x + dir[i][0], b = y + dir[i][1];
        if (a < 1 || a > n || b < 1 || b > m || h[x][y] <= h[a][b]) continue;
        
        f[x][y] = max(f[x][y], dp(a, b) + 1);
    }

    return f[x][y];
}

int main()
{
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; ++i)
    {
        for(int j = 1; j <= m; ++j)
        {
            scanf("%d", &h[i][j]);
        }
    }
    
    memset(f, -1, sizeof f);
    int res = 0;
    for(int i = 1; i <= n; ++i)
    {
        for(int j = 1; j <= m; ++j)
        {
            res = max(res, dp(i,j));
        }
    }
    
    printf("%d\n", res);
    
    return 0;
}
  • 22
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lijiachang030718

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值