NEUQ-ACM2022预备队第七周作业

第七周作业

动态规划

通过求解子问题的最优解,得到全局的最优解

动态规划的三要素

状态:求解一个子问题所需要的关键信息
转移式:如何通过子问题的最优解获得更大规模的问题的最优解
起始条件

动态规划的条件

最优子结构:可以通过子问题的最优解获得更大规模的问题的最优解
无后效性:已求解的子问题不影响后面的问题的求解
子问题重叠:需要多次使用同一个子问题的答案。第一次求解得到答案,下次使用时直接调用。

第一题

题目1

有一些草药,不同草药需要不同的采摘时间,每株草药需要一定的采摘时间和价值。给一个时间,问这段时间内能采摘得到的最大价值是多少。

思路1

符合动态规划的条件。求解用t时间采摘前n个草药能得到的最大价值,最终得到T时间采摘M个草药的最大价值。
状态:时间t和采摘前n个草药
状态转移方程:f(n, t) = max(f(n - 1, t), f(n - 1, t - ct[n]) + cv[n])。max前一个量为不采摘第n个草药的结果,后一个量为采摘第n个草药的结果。此外要判断t是否大于ct[n]。
起始条件:f(1, 0) = 0

代码1

#include<iostream>
#include<string.h>
using namespace std;

int main()
{
    //T、M
    int t, m;
    //采摘每个草药需要的时间、每个草药的价值、每个子问题的答案
    int ct[100], cv[100], ans[101][1001];
    //将初始答案全部设置为0(同时初始条件也设置好)
    memset(ans, 0, sizeof(ans));
    cin >> t >> m;
    for (int i = 1; i <= m; i++)
    {
        cin >> ct[i] >> cv[i];
    }
    //动态规划求解每个子问题的答案
    //外层循环为前i个草药,内存循环为花j时间
    for (int i = 1; i <= m; i++)
    {
        for (int j = 0; j <= t; j++)
        {
            //如果有足够的时间采摘
            if (j >= ct[i])
            {
                //状态转移方程
                ans[i][j] = max(ans[i - 1][j], ans[i - 1][j - ct[i]] + cv[i]);
            }
            //没有足够时间只能不采
            else
            {
                ans[i][j] = ans[i - 1][j];
            }
        }
    }
    //花t秒采摘m个草药的结果即位题目的答案
    cout << ans[m][t] << endl;

    return 0;
}

第二题

题目2

有一组数,按原顺序从这些数中取出数字,得到一个序列,并保证序列中的元素递增。问最长的序列的长度。(最长上升子序列)

思路2

符合动态规划的条件,用动态规划求解。子问题为以求解第n个数结尾的最长上升子序列。
状态:第n个数
状态转移方程:f(n) = max(f(n - j)) + 1(j取1到n-1的任意数,并且原序列s[i] > s[j])
起始条件:f(1) = 1

代码2

#include<iostream>
#include<string.h>
using namespace std;

int main()
{
    //n、原序列、子问题答案、题目答案(初始值为0)
    int n, s[5001], ans[5001], maxans = 0;
    cin >> n;
    for (int i = 1; i <= n; i++)
    {
        cin >> s[i];
        //设置所有子问题的答案初始值都是1(包含了起始条件)
        ans[i] = 1;
    }
    //动态规划
    //外层循环每个数,求解所有子问题的答案
    for (int i = 1; i <= n; i++)
    {
        //状态转移方程
        for (int j = 1; j < i; j++)
        {
            if (s[j] < s[i] && ans[j] + 1 > ans[i])
            {
                ans[i] = ans[j] + 1;
            }
        }
        //更新答案
        if (maxans < ans[i])
        {
            maxans = ans[i];
        }
    }

    cout << maxans << endl;
    return 0;
}

第三题

题目3

给出一个序列,求该序列中和最大的连续子序列

思路3

符合动态规划的条件。子问题为以第n个元素结尾的和最大的子序列。
状态:第n个元素
状态转移方程:f(n) = max(f(n - 1) + a[n], a[n])(如果这个数和前一个数结尾和最大的子序列组成新子序列的和更大,则使用这个子序列;如果不如这个数本身大,则只由这个数组成的子序列的和大)
起始条件:f(1) = a[1]

代码3

#include<iostream>
using namespace std;
int main()
{
    //长度n、序列、子问题的答案
    int n, s[200001], ans[200001];
    //最终答案:最大子序列的和
    int maxans = -2e9;

    cin >> n;
    //输入和动态规划
    //循环i为子问题的状态
    for (int i = 1; i <= n; i++)
    {
        cin >> s[i];
        //起始条件
        if (i == 1)
        {
            ans[i] = s[i];
        }
        else
        {
            //状态转移方程
            ans[i] = max(s[i], ans[i - 1] + s[i]);
            //更新答案
            if (maxans < ans[i])
            {
                maxans = ans[i];
            }
        }
    }
    
    cout << maxans << endl;
    return 0;
}

第四题

题目4

求字符串s和t的最长公共子序列

思路4

符合动态规划的条件。子问题为求解s前i个字符和t前j个字符中最长的公共子序列。
状态:s的前i个字符、t的前j个字符
条件转移方程:
f(i, j) = f(i - 1, j - 1) + 1(当s[i] == t[j]时,将这个字符选入子序列,最长的长度为f(i - 1, j - 1)加上1)
f(i, j) = max(f(i - 1, j) + f(i, j - 1))(当s[i] != t[j]时,不选这两个字符,最长长度为f(i - 1, j)(s向前移一位,t不变)和f(i, j - 1)(t向前移一位,s不变)较大的一个)
起始条件:f(i, 0) = 0, f(0, j) = 0

代码4

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

//子问题答案(第三维两个元素分别存长度和条件转移方程的类型(用来向前寻找所有的子序列中的字符))
int ans[3001][3001][2];

int main()
{
    //s和t
    string s, t;
    cin >> s >> t;
    //两个字符串的长度m和n
    int m = s.size(), n = t.size();
    //在s和t最开头插入一个任意字符,字符串中真正的内容从索引1开始,便于后面循环
    s.insert(s.begin(), ' ');
    t.insert(t.begin(), ' ');
    //动态规划
    //外层循环为状态i
    //内存循环为状态j
    for (int i = 1; i<= m; i++)
    {
        for (int j = 1; j <= n; j++)
        {
            //第一个状态方程的情况
            if (s[i] == t[j])
            {
                ans[i][j][0] = ans[i - 1][j - 1][0] + 1;
                //定义这种情况类型为3
                ans[i][j][1] = 3;
            }
            //第二个状态方程的情况
            else
            {
                //第二个状态方程前一个表达式大的情况
                if (ans[i - 1][j][0] >= ans[i][j - 1][0])
                {
                    ans[i][j][0] = ans[i - 1][j][0];
                    //定义这种情况类型为1
                    ans[i][j][1] = 1;
                }
                else
                {
                    //第二个状态方程后一个表达式大的情况
                    ans[i][j][0] = ans[i][j - 1][0];
                    //定义这种情况类型为2
                    ans[i][j][1] = 2;
                }
            }
        }
    }
    //最长公共子序列
    string r;
    //ans[m][n][0]即使最长子序列的长度,但是题目要求子序列的内容,需要向前循环得到所有子序列中的字符
    //循环得到子序列,由于不再使用m和n的值,直接使用m和n代表上面的i和j
    while (ans[m][n][1] != 0)
    {
        //情况3,说明子序列中包含s[m](或t[n]),加到子序列中
        if (ans[m][n][1] == 3)
        {
            r += s[m];
            //更新m和n
            m--;
            n--;
        }
        //情况2和情况1,子序列不包含当前位置的字符
        else if (ans[m][n][1] == 2)
        {
            //更新n
            n--;
        }
        else if (ans[m][n][1] == 1)
        {
            //更新m
            m--;
        }
    }
    //反向得到的字符需要反转
    reverse(r.begin(), r.end());
    cout << r << endl;
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值