[kuangbin带你飞]专题十二 基础DP1 解题报告(1 ~ 10)

A - Max Sum Plus Plus HDU - 1024
题意:在一个长度为n的序列中找出m个互不相交的区间,是它们的和最大。

思路:dp[i][j]表示以a[i]作为第 j 个区间的右端点所能获得的最大值。

动态转移方程:dp[i][j] = max(dp[i - 1][j], dp[k][j - 1]) (k 大于0小于 i ),该转移方程表示以 i 作为第 j 个区间的结尾可把a[i] 合并到以 i - 1作为第 j 个区间的结尾得到或者 a[i] 作为一个区间得到。

复杂度分析:若直接暴力写的代码是这样的

    for(int j = 1; j <= m; j++){
    	for(int i = j; i <= n; i++){ // 这里要i = j,因为第 j 个区间至少是从第 j 个数开始的
    		dp[i][j] = dp[i - 1][j] + a[i]; // 合并到上一个区间
    		for(int k = 1; k < i; k++){
    			dp[i][j] = max(dp[i][j], dp[k][j - 1] + a[i]);
    		}
    	}
    }

空间复杂度是n * m, 时间复杂度是 m * n ^ 2, 哦豁,完蛋,空间时间都不够。

我们先来优化时间,我们发现这步其实就是找上一维 1 到 i - 1 的最大值, 那我们在遍历 n 的时候就可以顺便记录最大值了,然后就直接拿来用,不用一个个找

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

接下来是空间,当然是滚动数组优化, 这样空间复杂度就是n, 时间复杂度就是 n * m

这是我自己写的代码, 好像int就能过,不过保险起见我开了LL

#include <stdio.h>
#include <algorithm>
using namespace std;
typedef long long LL;
const int maxn = 1e6 + 50;
LL INF = 1e18;
LL a[maxn];
LL dp[maxn];
int main(int argc, char const *argv[])
{
    int n, m;
    while(~scanf("%d%d", &m, &n)){
        for(int i = 1; i <= n; i++){
            scanf("%I64d", &a[i]);
            dp[i] = 0;
        }
        for(int j = 1; j <= m; j++){
            LL ma = -INF;
            for(int i = 1; i <= n; i++){
                LL x = dp[i];
                if(i >= j){
                    dp[i] = max(dp[i - 1] + a[i], ma + a[i]);
                } else{
                    dp[i] = -INF;
                }
                ma = max(ma, x);
            }
        }
        LL ans = -INF;
        for(int i = m; i <= n; i++){
            ans = max(ans, dp[i]);
        }
        printf("%I64d\n", ans);
    }
    return 0;
}

这是巨巨的代码

#include <stdio.h>
#include <algorithm>
using namespace std;
typedef long long LL;
const int maxn = 1e6 + 50;
LL INF = 1e18;
LL a[maxn];
LL dp[maxn];
LL ma[maxn];
int main(int argc, char const *argv[])
{
	int n, m;
	while(~scanf("%d%d", &m, &n)){
		for(int i = 1; i <= n; i++){
			scanf("%I64d", &a[i]);
			dp[i] = ma[i] = 0;
		}
		LL MA;
		for(int j = 1; j <= m; j++){
			MA = -INF;
			for(int i = j; i <= n; i++){
				dp[i] = max(dp[i - 1], ma[i - 1]) + a[i];
				ma[i - 1] = MA;
				MA = max(MA, dp[i]); 
			}
		}
		printf("%I64d\n", MA);
	}
	return 0;
}

B - Ignatius and the Princess IV HDU - 1029
思路:利用好(n + 1) / 2这个条件就好了

#include<cstdio>
using namespace std;
int main()
{
    int n;
    while(~scanf("%d", &n)){
        int cnt = 0;
        int ans = 0;
        for(int i = 0; i < n; i++){
            int x;
            scanf("%d", &x);
            if(cnt == 0){
                ans = x;
                cnt = 1;
            } else{
                if(ans == x){
                    cnt++;
                } else{
                    cnt--;
                }
            }
        }
        printf("%d\n", ans);
    }
    return 0;
}

C - Monkey and Banana HDU - 1069
思路:dp[i][j]表示第 i 个箱子在第 j 层时的最大高,因为n最大是30,我们把每个箱子看成三个箱子,在一个塔中,一个箱子最多出现两次(自己画一下,很容易看出来),所以最高是n * 6层的塔
状态转移方程:dp[i][cnt] = max(dp[i][cnt], dp[j][cnt - 1] + blocks[i].h);
复杂度分析:因为最高是6 * n层,所以复杂度为 6 * n * 3 * n * 3 * n = 54 * n ^ 3

#include<cstdio>
#include <algorithm>
using namespace std;
typedef long long LL;
const int maxn = 1e6 + 50;
struct date
{
    int ba1, ba2, h;
} blocks[maxn];

int dp[200][200];
int main()
{
    int n;
    int ca = 1;
    while(~scanf("%d", &n)){
        if(n == 0){
            break;
        }
        int k = 0;
        for(int i = 0; i < 200; i++){
            for(int j = 0; j < 200; j++){
                dp[i][j] = 0;
            }
        }
        int ans = 0;

        for(int i = 1; i <= n; i++){
            int a, b, c;
            scanf("%d%d%d", &a, &b, &c);
            blocks[++k].ba1 = a, blocks[k].ba2 = b, blocks[k].h = c;
            dp[k][1] = c;
            blocks[++k].ba1 = b, blocks[k].ba2 = c, blocks[k].h = a;
            dp[k][1] = a;
            blocks[++k].ba1 = a, blocks[k].ba2 = c, blocks[k].h = b;
            dp[k][1] = b;
            ans = max(ans, max(a, max(b, c)));
        }
        int cnt = 2;
        while(cnt){
            int ma = 0;
            for(int i = 1; i <= k; i++){
                for(int j = 1; j <= k; j++){
                    if(max(blocks[i].ba1, blocks[i].ba2) < max(blocks[j].ba1, blocks[j].ba2) && min(blocks[i].ba1, blocks[i].ba2) < min(blocks[j].ba1, blocks[j].ba2)){
                        dp[i][cnt] = max(dp[i][cnt], dp[j][cnt - 1] + blocks[i].h);
                        ma = max(dp[i][cnt], ma);
                    }
                }
            }
            cnt++;
            if(cnt == n * 6 + 1){
                break;
            }
            ans = max(ans, ma);
        }

        printf("Case %d: maximum height = %d\n", ca++, ans);
    }
    return 0;
}

D - Doing Homework HDU - 1074
思路:状压dp,用pre记录路径,题目要求输出的字典序最小,所以我们要让字典序大的尽量在后面解决
状态转移方程:dp[i].cost = min(dp[from].cost + c, dp[i].cost)

#include<cstdio>
#include <algorithm>
#include <string>
#include <iostream>
using namespace std;
const int maxn = 1e6 + 50;
int INF = 1e8;
struct node
{
    int pre, cost, now, time;
} dp[1 << 15];

struct date
{
    string s;
    int d, t;
} a[20];

void print(int t){ // 递归输出路径
    int pre = dp[t].pre;
    if(dp[pre].now != -1){
        print(pre);
    }
    cout << a[dp[t].now].s << endl;
}
int main()
{
    int tt;
    cin >> tt;
    while(tt--){
        int n;
        cin >> n;
        for(int i = 1; i <= n; i++){
            cin >> a[i].s >> a[i].d >> a[i].t;
        }
        dp[0].now = -1;
        for(int s = 1; s < (1 << n); s++){
            dp[s].cost = INF;
            for(int i = 1; i <= n; i++){
                if(s & (1 << (i - 1))){
                    int from = s - (1 << (i - 1));
                    int c = dp[from].time + a[i].t - a[i].d;
                    if(c < 0){
                        c = 0;
                    }
                    if(dp[s].cost >= dp[from].cost + c){ // 因为要字典序最小,所以这里必须是 >= 
                        dp[s].cost = dp[from].cost + c;
                        dp[s].now = i;
                        dp[s].time = dp[from].time + a[i].t;
                        dp[s].pre = from;
                    }
                }
            }
        }

        int ans = dp[(1 << n) - 1].cost;
        printf("%d\n", ans);
        print((1 << n) - 1); 
    }
    return 0;
}

E - Super Jumping! Jumping! Jumping! HDU - 1087
思路:dp[i]表示以 i 为结束的点所能获得的最大价值
状态转移方程:dp[i] = max(dp[j] + a[i], dp[i]);(a[i] > a[j])

#include<cstdio>
#include <algorithm>
#include <string>
#include <iostream>
using namespace std;
const int maxn = 1e6 + 50;
int INF = 1e8;
int a[maxn];

int dp[1005];
int main()
{
    int n;
    while(~scanf("%d", &n)){
        if(n == 0){
            break;
        }
        for(int i = 1; i <= n; i++){
            scanf("%d", &a[i]);
            dp[i] = a[i];
        }
        for(int i = 1; i <= n; i++){
            for(int j = 1; j < i; j++){
                if(a[j] < a[i]){
                    dp[i] = max(dp[j] + a[i], dp[i]);
                }
            }
        }

        int ans = -INF;
        for(int i = 1; i <= n; i++){
            ans = max(ans, dp[i]);
        }
        printf("%d\n", ans);
    }
    return 0;
}

F - Piggy-Bank HDU - 1114
思路:类似于完全背包,但状态转移时要判断一下能不能转移
状态转移方程:**dp[j] = min(dp[j], dp[j - a[i].w] + a[i].p);(dp[j - a[i].w] != INF)**意思时在
**dp[j - a[i].w]**存在时才可以转移

#include<cstdio>
#include <algorithm>
#include <string>
#include <iostream>
using namespace std;
const int maxn = 1e6 + 50;
int INF = 1e8;
int dp[10005];
struct date
{
    int p, w;
} a[maxn];

bool cmp(date A, date B){
    return A.w < B.w;
}
int main()
{
    int t;
    scanf("%d", &t);
    while(t--){
        int e, f;
        scanf("%d%d", &e, &f);
        int n;
        scanf("%d", &n);
        for(int i = 1; i <= n; i++){
            scanf("%d%d", &a[i].p, &a[i].w);
        }
        sort(a + 1, a + n + 1, cmp);
        int up = f - e;
        for(int i = 1; i <= up; i++){
            dp[i] = INF;
        }

        for(int i = 1; i <= n; i++){
            for(int j = a[i].w; j <= up; j++){
                if(dp[j - a[i].w] != INF){
                    dp[j] = min(dp[j], dp[j - a[i].w] + a[i].p);
                }
            }
        }
        if(dp[up] == INF){
            printf("This is impossible.\n");
        } else{
            printf("The minimum amount of money in the piggy-bank is %d.\n", dp[up]);
        }
    }
    return 0;
}

G - 免费馅饼 HDU - 1176
思路:这题正着dp会发现状态转移很难写,因为有些状态时不能到达的,所以我们反着dp,最后输出dp[0][5]的值就好了,dp[i][j]表示在时间 i 的时候处于 点 j 可获得的最大数量
状态转移方程:
dp[i][0] = max(dp[i + 1][0], dp[i + 1][1]) + num[i][0];
dp[i][j] = max(dp[i + 1][j - 1], max(dp[i + 1][j], dp[i + 1][j + 1])) + num[i][j];(1 <= i <= 9)
dp[i][10] = max(dp[i + 1][10], dp[i + 1][9]) + num[i][10];

#include<cstdio>
#include <algorithm>
#include <string>
#include <iostream>
using namespace std;
const int maxn = 1e5 + 50;
int INF = 1e8;
int dp[maxn][20];
int num[maxn][20];
int main()
{
    int n;
    while(~scanf("%d", &n)){
        if(n == 0){
            break;
        }
        int mat = 0;
        for(int i = 0; i <= maxn; i++){
            for(int j = 0; j <= 10; j++){
                dp[i][j] = num[i][j] = 0;
            }
        }
        for(int i = 1; i <= n; i++){
            int t, x;
            scanf("%d%d", &x, &t);
            num[t][x]++;
            mat = max(mat, t);
        }
        for(int i = mat; i >= 0; i--){
            dp[i][0] = max(dp[i + 1][0], dp[i + 1][1]) + num[i][0];
            for(int j = 1; j < 10; j++){
                dp[i][j] = max(dp[i + 1][j - 1], max(dp[i + 1][j], dp[i + 1][j + 1])) + num[i][j];
            }
            dp[i][10] = max(dp[i + 1][10], dp[i + 1][9]) + num[i][10];
        }
        printf("%d\n", dp[0][5]);
    }
    return 0;
}                             

H - Tickets HDU - 1260
思路:dp[i][0]表示第 i 个人买个人票所用的最小时间, dp[i][1]表示第 i 个人买双人票所用的最小时间
min(dp[n - 1][1], dp[n][0]) 就是所需的最小时间了
状态转移方程:
dp[i][0] = min(dp[i - 1][0], dp[i - 2][1]) + a[i].one;
dp[i][1] = min(dp[i - 1][0], dp[i - 2][1]) + a[i].two;

#include<cstdio>
#include <algorithm>
#include <string>
#include <iostream>
using namespace std;
const int maxn = 1e5 + 50;
int INF = 1e8;

struct date
{
    int one, two;
} a[maxn];
int dp[maxn][2];
int main()
{
    int t;
    scanf("%d", &t);
    while(t--){
        int n;
        scanf("%d", &n);
        for(int i = 1; i <= n; i++){
            dp[i][0] = dp[i][1] = INF;
            scanf("%d", &a[i].one);
        }
        for(int i = 1; i < n; i++){
            scanf("%d", &a[i].two);
        }
        a[n].two = INF;
        dp[1][0] = a[1].one;
        dp[1][1] = a[1].two;
        dp[0][0] = dp[0][1] = INF;
        for(int i = 2; i <= n; i++){
            dp[i][0] = min(dp[i - 1][0], dp[i - 2][1]) + a[i].one;
            dp[i][1] = min(dp[i - 1][0], dp[i - 2][1]) + a[i].two;
        }
        int res = min(dp[n - 1][1], dp[n][0]);
        int t = 8 * 60 * 60;
        t += res;
        int h = t / 3600;
        t -= h * 3600;
        int m = t / 60;
        t -= m * 60;
        printf("%02d:%02d:%02d ", h, m, t);
        if(h >= 12){
            printf("pm\n");
        } else{
            printf("am\n");
        }
    }
    return 0;
}

I - 最少拦截系统 HDU - 1257
思路:贪心就完事了。。。

#include <stdio.h>
#include <algorithm>
#include <string.h>
using namespace std;

int a[1000000] = {0};
int main(){
    int t;
    while(~scanf("%d", &t)){
        int x;
        int flag = 1;
        memset(a, 0, sizeof(a));
        for(int i = 0; i < t; i++){
            scanf("%d", &x);
            int j;
            for(j = 0; j < flag; j++){
                if(a[j] >= x){
                    a[j] = x;
                    break;
                }
            }
            if(j == flag){
                a[flag++] = x;
            }
        }
        printf("%d\n", flag - 1);
    }
    return 0;
}

J - FatMouse’s Speed HDU - 1160
思路:类似于最长上升子序列,这里n只用1000,n ^ 2的复杂度就够了,因为要记录路径,我们加一个pre数组
状态转移方程:
dp[i] = max(dp[i], dp[j] + 1) (a[j].w < a[i].w && a[j].s > a[i].s)

#include<cstdio>
#include <algorithm>
#include <string>
#include <iostream>
#include <queue>
#include <stack>
using namespace std;
const int maxn = 1e5 + 50;
int INF = 1e8;

struct date
{
    int id;
    int w, s;    
} a[maxn];

bool cmp(date A, date B){
    if(A.w != B.w){
        return A.w < B.w;
    } else{
        return A.s < B.s;
    }
}

int dp[maxn];
int pre[maxn];
int main()
{
    int n = 0;
    int w, s;
    while(~scanf("%d%d", &w, &s)){
        a[++n].w = w;
        a[n].s = s;
        a[n].id = n;
    }
    sort(a + 1, a + n + 1, cmp);

    dp[1] = 1;
    int ans = 1;
    for(int i = 1; i <= n; i++){
        dp[i] = 1;
        for(int j = 1; j < i; j++){
            if(a[j].w < a[i].w && a[j].s > a[i].s){
                if(dp[i] < dp[j] + 1){
                    
                    dp[i] = dp[j] + 1;
                    ans = max(ans, dp[i]);
                    pre[i] = j;
                }
            }
        }
    }
    printf("%d\n", ans);
    int st = 0;
    for(int i = n; i >= 1; i--){
        if(dp[i] == ans){
            st = i;
            break;
        }
    }

    stack<int> stk;
    stk.push(a[st].id);
    while(pre[st]){
        st = pre[st];
        stk.push(a[st].id);
    }

    while(stk.size()){
        int x = stk.top();
        stk.pop();
        printf("%d\n", x);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值