蓝桥杯零基础冲国赛-第6天

典型题型代码演示加附加内容

一、最长上升子序列

有一个数字序列,求其中最长上升子序列的长度。

解题过程:

1、确定动归状态

例如:dp(i) 代表以i为结尾的上身子序列

2、确定状态转移方程

例如:dp(i) = max{dp(j)} + 1

3、时间复杂度分析

求解dp(i)需要遍历i - 1次,所以总体时间复杂度为O(n ^ 2)

#include<iostream>
#include <vector>
using namespace std;

int Max_List(vector<int> &ary) {
    int dp[ary.size() + 1];
    int ans = 0;
    for (int i = 0; i < ary.size(); i++) {
        dp[i] = 1;
        for (int j = 0; j <= i; j++) {
            if (ary[i] > ary[j]) {
                dp[i] = max(dp[i], dp[j] + 1);
                ans = max(ans, dp[i]);
            }
        }
    }
    return ans;
}

int main() {
    int n;
    cin >> n;
    vector<int> ary(n);
    for (int i = 0; i < n; i++) {
        int a;
        cin >> a;
        ary[i] = a;
    }
    cout << Max_List(ary) << endl;
    return 0;
}

二、最长公共子序列

给出两个字符串,求其两个的最长公共子序列。

解题过程:

1、确定动归状态

dp(i, j) 代表 A串长度为i位,B串长度为j位的最长公共子序列长度

2、确定状态转移方程

dp(i, j) = max { dp(i, j - 1)

​ dp(i - 1, j)

​ dp(i - 1, j - 1) + (A[i] == B[j])}

3、分析转移方程的意义

第一项和第二项可以理解成为是A[i] 和B[j] 不相等的情况

第三项当A[i] 与 B[j] 相等时,才有意义,不过不影响

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

char s1[1005], s2[1005];

int main() {
    cin >> s1;
    cin >> s2;
    int dp[1005][1005];
    memset(dp, 0, sizeof(dp));

    for (int i = 1; i <= strlen(s1); i++) {
        for (int j = 1; j <= strlen(s2); j++) {
            if (s1[i - 1] == s2[j - 1]) {
                dp[i][j] = max(dp[i - 1][j - 1] + 1, dp[i][j]);
            } else {
                dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
            }
        }
    }
    cout << dp[strlen(s1)][strlen(s2)];
    return 0;
}

三、切割回文

给出一个字符串S,问对字符串S最少切几刀,使得分成的每一部分都是一个回文串(注意,单一字符,是回文串)

解题过程:

1、确定动归状态

dp(i) 以i位置为结尾最少能分割回文串数量

2、确定状态转移方程

dp(i) = max{dp(j)} + 1

3、分析转移方程的意义

从j + 1位置到 i 位置是一段合法的回文串

#include<iostream>
#include <vector>
#include <cstring>
using namespace std;
char str[500005];
vector<vector<int> > g(500005);
int dp[500005];

void extract(int i, int j) {
    while (i >= 0 && str[i] == str[j]) {
        g[j + 1].push_back(i);
        i--, j++;
    }
    return ;
}
int main() {
    scanf("%s", str);
    for (int i = 0; i < strlen(str); i++) {
        extract(i, i);
        extract(i, i + 1);
    }
    dp[0] = 0;
    //dp[i]为以i位置为结尾最少能分割回文串数量
    for (int i = 1; i <= strlen(str); i++) {
        dp[i] = i;
        for (auto x : g[i]) {
            dp[i] = min(dp[x] + 1, dp[i]);
        }
    }
    cout << dp[strlen(str)] - 1 << endl;

    return 0;
}

解题二:

1、确定动归状态

dp(i, j) 代表从i到j最少切多少刀

2、确定状态转移方程

dp(i, j) = min { dp(i, k) + dp(k + 1, j) + 1 | i <= k < j

​ 0 S[i] = S[j] 且 dp(i + 1, j - 1) = 0}

3、分析转移方式

大区间的结果由其包含的小区间确定,转移方式就应该是小区间的结果,逐渐增大区间范围。

很容易确定所有dp(i, i) 的值

第二步确定所有dp(i, i + 1)的值

第三步确定所有dp(i , i + 2) 的值

四、0/1背包(资源受限的最优化的求解问题)

给有一个能承重V的背包,和n件物品,我们用重量和价值的二元组来表示一个物品,第i件物品表示(Vi, Wi),问在背包不超重的情况下,得到物品的最大价值是多少?

解题过程:

思考:到底是什么影响了背包的总价值。一个是物品的种类,一个的背包的能承载的重量

1、确定动归状态

dp(i, j)代表前i件物品,背包承重为j时所获得的最大价值

2、确定状态转移方程

dp(i,j) = max{dp[i - 1] [j] | 不选第i种物品 -> 0

​ dp[i - 1] [ j - v[i] ] + w[i] } | 选第i件物品 -> 1

3、填表格

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kfPeUPvL-1642581824268)(C:\Users\86166\AppData\Roaming\Typora\typora-user-images\image-20220118110309215.png)]

#include<iostream>
#include <cstring>
using namespace std;
int dp[105][10005];
int v[105], w[105];

int main() {
    int V, n;
    cin >> V >> n;

    for (int i = 1; i <= n; i++) {
        cin >> v[i] >> w[i];
    }
    memset(dp, 0, sizeof(dp));

    for (int i = 1; i <= n; i++) {
        for (int j = 0; j <= V; j++) {
            dp[i][j] =  dp[i - 1][j];
            if (j >= v[i]) dp[i][j] = max(dp[i][j], dp[i - 1][j - v[i]] + w[i]);
        }
    }
    printf("%d\n", dp[n][V]);
    return 0;
}

五、完全背包

给有一个能承重V的背包,和n种物品,每种物品任意多,我们用重量和价值的二元组来表示一个物品,第i种物品表示为(Vi,Wi),问在背包不超重的情况下,得到物品的最大价值是多少?

1、确定动归状态

dp(i, j) 代表前i件物品,背包沉重为j时所获得的最大价值

2、确定状态转移方程

dp(i,j) = max{dp[i - 1] [j] | 不选第i种物品 -> 0

​ dp[i] [ j - v[i] ] + w[i] } | 选第i件物品 -> 1

3、填表格

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aIlBaNyB-1642581824269)(C:\Users\86166\AppData\Roaming\Typora\typora-user-images\image-20220118110309215.png)]

#include<iostream>
#include <cstring>
using namespace std;
int dp[2][10005];
int v[10005], w[10005];

int main() {
    int V, n;
    cin >> n >> V;

    for (int i = 1; i <= n; i++) {
        cin >> v[i] >> w[i];
    }
    memset(dp, 0, sizeof(dp));

    for (int i = 1; i <= n; i++) {
        int pre_ind = ( i - 1 ) % 2, ind =  i % 2;
        for (int j = 0; j <= V; j++) {
            dp[ind][j] =  dp[pre_ind][j];
            if (j >= v[i]) dp[ind][j] = max(dp[ind][j], dp[ind][j - v[i]] + w[i]);
        }
    }
    printf("%d\n", dp[n % 2][V]);
    return 0;
}

六、多重背包

给有一个能承重V的背包,和n种物品,每种物品的数量有限多,我们用重量、价值和数量的三元组来表示一种物品,第i种物品表示为(Vi,Wi,Si),问在背包不超重的情况下,得到物品的最大价值是多少?

1、确定动归状态

dp(i, j) 代表前i件物品,背包沉重为j时所获得的最大价值

2、确定状态转移方程

dp(i,j) = max{dp[i - 1] [j - k*v[i]] + k * w[i] } 第i种物品选择k件

3、填表格

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hIArUUge-1642581824270)(C:\Users\86166\AppData\Roaming\Typora\typora-user-images\image-20220118110309215.png)]

动归问题总结:

1、动态规划是一类特殊的递推问题

满足递推问题求解的三步走原则

2、最优化问题

最大、最小、最长…

3、最优子结构

规模为n的最优解,取决于规模为n - 1的最优解

4、无后效性

之前的决策对后续决策无影像

#include<iostream>
#include <cstring>
using namespace std;
int dp[105][10005];
int v[105], w[105], s[105];

int main() {
    int V, n;
    cin >> n >> V;

    for (int i = 1; i <= n; i++) {
        cin >> v[i] >> w[i] >> s[i];
    }
    memset(dp, 0, sizeof(dp));

    for (int i = 1; i <= n; i++) {
        for (int j = 0; j <= V; j++) {
            dp[i][j] =  dp[i - 1][j];
            for (int k = 1; k <= s[i] && k * v[i] <= j; k++) { 
                dp[i][j] = max(dp[i][j], dp[i - 1][j - k * v[i]] + k * w[i]);
            }
        }
    }
    printf("%d\n", dp[n][V]);
    return 0;
}

重点典型题目训练:

洛谷 | 计算机科学教育新生态

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-agCtlIRP-1642581824270)(C:\Users\86166\AppData\Roaming\Typora\typora-user-images\image-20220119085333053.png)]
预测:之前的代码的复杂度很高,明天优化动态规划作法

在这里插入图片描述

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

扑天鹰

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

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

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

打赏作者

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

抵扣说明:

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

余额充值