NEUQ-acm 预备队训练Week7—动态规划问题(DP)

DP

动态规划问题:01背包,最长上升子序列,区间dp,概率dp
递推:拆分成子问题,解决原问题

P1048 [NOIP2005 普及组] 采药

题目描述

辰辰是个天资聪颖的孩子,他的梦想是成为世界上最伟大的医师。为此,他想拜附近最有威望的医师为师。医师为了判断他的资质,给他出了一个难题。医师把他带到一个到处都是草药的山洞里对他说:“孩子,这个山洞里有一些不同的草药,采每一株都需要一些时间,每一株也有它自身的价值。我会给你一段时间,在这段时间里,你可以采到一些草药。如果你是一个聪明的孩子,你应该可以让采到的草药的总价值最大。”

如果你是辰辰,你能完成这个任务吗?

输入格式

第一行有 2 2 2 个整数 T T T 1 ≤ T ≤ 1000 1 \le T \le 1000 1T1000)和 M M M 1 ≤ M ≤ 100 1 \le M \le 100 1M100),用一个空格隔开, T T T 代表总共能够用来采药的时间, M M M 代表山洞里的草药的数目。

接下来的 M M M 行每行包括两个在 1 1 1 100 100 100 之间(包括 1 1 1 100 100 100)的整数,分别表示采摘某株草药的时间和这株草药的价值。

输出格式

输出在规定的时间内可以采到的草药的最大总价值。

样例输入

70 3
71 100
69 1
1 2

样例输出

3

提示

【数据范围】

  • 对于 30 % 30\% 30% 的数据, M ≤ 10 M \le 10 M10
  • 对于全部的数据, M ≤ 100 M \le 100 M100

【题目来源】

NOIP 2005 普及组第三题

思路

1.记忆化搜索
考虑第 i i i个草药
如果采第 i i i个草药,则需要知道时间为 j − t i j-t_i jti之前采了 i − 1 i-1 i1个草药所得到的最大价值
如果不采第 i i i个草药,则需要知道时间为 j j j之前采了 i − 1 i-1 i1个草药所得到的最大价值
所以rem[i][j]来表示选择采前 i i i个草药的几种在时间 j j j内所能达到的最大价值
2.递推
r e m [ i ] [ j ] = { r e m [ i − 1 ] [ j ] v[i] > j m a x ( r e m [ i − 1 ] [ j ] , r e m [ i − 1 ] [ j − v [ i ] ] + t [ i ] ) v[i]  ≤  j rem[i][j]= \begin{cases} rem[i-1][j] & \text{v[i] > j} \\ max(rem[i-1][j], rem[i-1][j-v[i]]+t[i]) & \text{v[i] $\leq$ j}\\ \end{cases} rem[i][j]={rem[i1][j]max(rem[i1][j],rem[i1][jv[i]]+t[i])v[i] > jv[i]  j
起始条件 r e m [ 0 ] [ j ] = 0 rem[0][j]=0 rem[0][j]=0

代码1 递归

#include 
using namespace std;
int T, M;
struct Cao{
    int time;
    int value;
    bool operator< (Cao b){
        return time > b.time || (time==b.time && value > b.value);
    }
    bool operator> (Cao c) const{
        return time > c.time || (time==c.time && value > c.value);
    }
}C[105];
int ans;
int res[105][1005];
int dfs(int i, int j){
    if(res[i][j]!=-1) return res[i][j];
    if(i==0) ans=0;
    else if(C[i].time > j)  ans=dfs(i-1, j);
    else  ans = max(dfs(i-1, j), dfs(i-1, j-C[i].time)+C[i].value);
    res[i][j]=ans;
    return ans;
}

int main(){
    cin >> T >> M;
    for(int i = 1; i <= M; i++){
        cin >> C[i].time >> C[i].value;
    }
    memset(res, -1, sizeof(res));
    sort(C+1, C+M+1, greater<Cao>());
    dfs(M, T);
    cout << ans;
}

注意:大于号重载时要加const,否则编译错误~

代码2 递推

#include 
using namespace std;
int T, M;
struct Cao{
    int time;
    int value;
}C[105];
int ans;
int res[105][1005];
int main(){
    cin >> T >> M;
    for(int i = 1; i <= M; i++){
        cin >> C[i].time >> C[i].value;
    }
    memset(res, 0, sizeof(res));
    for(int i = 1; i <= M; i++){
        for(int j = 0; j <= T; j++){
            res[i][j]=res[i-1][j];
            if(j >= C[i].time)
                res[i][j]=max(res[i][j], res[i-1][j-C[i].time]+C[i].value);
        }
    }
    cout << res[M][T];
}

B3637 最长上升子序列

题目描述

这是一个简单的动规板子题。

给出一个由 n ( n ≤ 5000 ) n(n\le 5000) n(n5000) 个不超过 1 0 6 10^6 106 的正整数组成的序列。请输出这个序列的最长上升子序列的长度。

最长上升子序列是指,从原序列中按顺序取出一些数字排在一起,这些数字是逐渐增大的。

输入格式

第一行,一个整数 n n n,表示序列长度。

第二行有 n n n 个整数,表示这个序列。

输出格式

一个整数表示答案。

样例输入 #1

6
1 2 4 1 3 4

样例输出 #1

4

提示

分别取出 1 1 1 2 2 2 3 3 3 4 4 4 即可。

思路

考虑第 i i i个是否可以加入到上升子序列
遍历前 i − 1 i-1 i1个,如果当前数大(可以加入上升子序列中),则比较当前的子序列与构成的新子序列谁的元素多,并更新
选取最长的上升子序列

代码

#include 
using namespace std;
int res[5005];
int list1[5005];
int n;
int main(){
    cin >> n;
    for(int i = 0; i < n; i++)  cin >> list1[i];
    for(int i = 0; i < n; i++) res[i]=1;
    for(int i = 1; i < n; i++){
        for(int j = 0; j < i; j++){
            if(list1[i]>list1[j]) res[i]=max(res[i], res[j]+1);
        }
    }
    sort(res, res+n);
    cout << res[n-1];
}

P1115 # 最大子段和

题目描述

给出一个长度为 n n n 的序列 a a a,选出其中连续且非空的一段使得这段和最大。

输入格式

第一行是一个整数,表示序列的长度 n n n

第二行有 n n n 个整数,第 i i i 个整数表示序列的第 i i i 个数字 a i a_i ai

输出格式

输出一行一个整数表示答案。

样例输入 #1

7
2 -4 3 -1 2 -4 3

样例输出 #1

4

提示

样例 1 解释

选取 [ 3 , 5 ] [3, 5] [3,5] 子段 { 3 , − 1 , 2 } \{3, -1, 2\} {3,1,2},其和为 4 4 4

数据规模与约定

  • 对于 40 % 40\% 40% 的数据,保证 n ≤ 2 × 1 0 3 n \leq 2 \times 10^3 n2×103
  • 对于 100 % 100\% 100% 的数据,保证 1 ≤ n ≤ 2 × 1 0 5 1 \leq n \leq 2 \times 10^5 1n2×105 − 1 0 4 ≤ a i ≤ 1 0 4 -10^4 \leq a_i \leq 10^4 104ai104

思路

递推,考虑第 i i i个的最大子序列和
发现只与第 i − 1 i-1 i1个有关
比较把第 i i i个加入子序列和把第 i i i个当作第一个子序列元素,哪一个和最大

代码

#include 
using namespace std;
const int Max = 2*1e5+5;
int n;
int list1[Max];
int res[Max];
int main(){
    cin >> n;
    for(int i = 0; i < n; i++){
        cin >> list1[i];
    }
    memset(res,0,sizeof(res));
    res[0]=list1[0];
    for(int i = 1; i < n; i++){
        res[i]=max(res[i-1]+list1[i],list1[i]);
    }
    sort(res,res+n);
    cout << res[n-1];
}

AT_dp_f 最长公共子序列

题目描述

给定一个字符串 s s s 和一个字符串 t t t ,输出 s s s t t t 的最长公共子序列。

输入格式

两行,第一行输入 s s s ,第二行输入 t t t

输出格式

输出 s s s t t t 的最长公共子序列。如果有多种答案,输出任何一个都可以。

样例输入 #1

axyb
abyxb

样例输出 #1

axb

样例输入 #2

aa
xayaz

样例输出 #2

aa

样例输入 #3

a
z

样例输出 #3


样例输入 #4

abracadabra
avadakedavra

样例输出 #4

aaadara

说明/提示

数据保证 s s s t t t 仅含英文小写字母,并且 s s s t t t 的长度小于等于3000。

思路

二维数组res[i][j] a的前 i i i个元素和b的前 j j j个元素中,最长公共子序列的长度。
递推:
r e s [ i ] [ j ] = { r e m [ i − 1 ] [ j − 1 ] + 1 a[i]=b[j] m a x ( r e m [ i − 1 ] [ j ] , r e m [ i ] [ j − 1 ] ) a[i] ≠ b[j] res[i][j]= \begin{cases} rem[i-1][j-1]+1 & \text{a[i]=b[j]} \\ max(rem[i-1][j], rem[i][j-1]) & \text{a[i]$\neq$b[j]}\\ \end{cases} res[i][j]={rem[i1][j1]+1max(rem[i1][j],rem[i][j1])a[i]=b[j]a[i]=b[j]
难点:得到子序列元素
如果a[i]=b[j] ,那么刚好这个元素就是当前子序列第res[i][j]个的元素;
如果不相等,看res[i][j]是从哪来的,就退回去,直到上一条件的时候

代码

#include 
using namespace std;
const int Max = 3005;
int res[Max][Max];
string a,b;
char ans[Max];
int main(){
    cin >> a;
    cin >> b;
    int na = a.length();
    int nb = b.length();
    memset(res, 0, sizeof(res));
    for(int i = 1; i <= na; i++){
        for(int j = 1; j <= nb; j++){
            if(a[i-1]==b[j-1]){
                res[i][j] = res[i-1][j-1]+1;
            }
            else{
                res[i][j] = max(res[i-1][j], res[i][j-1]);
            }
        }
    }
    int num = res[na][nb];
    while(res[na][nb]>0){
        int ind = res[na][nb];
        if(a[na-1]==b[nb-1]){
            ans[ind-1] = a[na-1];
            na--;
            nb--;
        }
        else{
            if(res[na][nb]==res[na-1][nb]) na--;
            else nb--;
        }
    }
    for(int i = 0; i < num; i++){
        cout << ans[i];
    }
}

记录一个经常犯得小错误:总把 j j j 写成 i i i (太致命啦~)

你可能感兴趣的:(ACM预备队训练,动态规划,算法,深度优先,c++)