曾经沧海难为水,除却巫山不是云。|

Joey-Wang

园龄:4年3个月粉丝:17关注:0

11.8 动态规划专题总结

11.8 动态规划专题总结

http://codeup.hustoj.com/contest.php?cid=100000632

A 第二题 ❓

image-20200903015140498

题目解析

1️⃣ 做法一:使用DFS,搜索每一个最小于等于sum/2的子数组,并且找出其中和最接近sum/2的子数组。
一开始写得dfs代码👇时间超时50

void dfs(int index, int total, int sum) {
    if (total > sum) return;
    if (total == sum || index == n) { //结束条件
        if (total > sum1) sum1 = total;
        return;
    }
    dfs(index + 1, total + a[index], sum);
    dfs(index + 1, total, sum);

后来剪枝优化的DFS代码👇但还是时间超时50🤣 _(:з」∠)_

void dfs(int index, int total, int sum) {
    if (index == n) return;
    dfs(index + 1, total, sum); //不选第index号物品
    if (total + a[index] <= sum) { //加入index号物品,和不超过sum才继续
        if (total + a[index] > sum1) {
            sum1 = total + a[index];
        }
        dfs(index + 1, total + a[index], sum);
    }
}

2️⃣ 做法二:使用DP,使用01背包的做法,寻找容量为sum/2的最优解
因为只有一个维度——元素大小,这相当于物品的重量,则将元素的大小也作为物品的价值
dp[v] = max(dp[v], dp[v - a[j]] + a[j])
但最后运行错误50,可能是内存超限,不清楚

🔵 最后在网上找到了一个DFS的AC代码,但觉得和我的差不多,不知道为啥我就超时
https://blog.csdn.net/qq_41773202/article/details/104917949

代码(DFS——WA)

#include <cstdio>
#include <iostream>
#include <string>
#define maxn 100
using namespace std;
int a[maxn], n, sum1;

void dfs(int index, int total, int sum) {
    if (index == n) return;
    dfs(index + 1, total, sum); //不选第index号物品
    if (total + a[index] <= sum) { //加入index号物品,和不超过sum才继续
        if (total + a[index] > sum1) {
            sum1 = total + a[index];
        }
        dfs(index + 1, total + a[index], sum);
    }
}

int main() {
    string s;
    int temp, sum;
    while (getline(cin, s)) {
        n = 0, temp = 0, sum = 0, sum1 = 0;
        int i;
        for (i = 0; i <= s.length(); i++) {
            if (s[i] >= '0' && s[i] <= '9') {
                temp = temp * 10 + s[i] - '0';
            } else if (s[i] == ' ' || s[i] == '\0') {
                a[n] = temp;
                sum += temp;
                n++;
                temp = 0;
            } else break;
        }
        if (i <= s.length()) printf("ERROR\n");
        else {
            dfs(0, 0, sum / 2);
            int sum2 = sum - sum1;
            if (sum1 > sum2) printf("%d %d\n", sum1, sum2);
            else printf("%d %d\n", sum2, sum1);
        }
    }
    return 0;
}

代码(DP——WA)

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

#define maxn 100010
#define maxv 100010
using namespace std;
long long a[maxn];
long long dp[maxv];

int main() {
    string s;
    int n, temp;
    long long sum, sum1;
    while (getline(cin, s)) {
        memset(dp, 0, sizeof(dp));
        n = 1, temp = 0, sum = 0, sum1 = 0;
        int i;
        for (i = 0; i <= s.length(); i++) {
            if (s[i] >= '0' && s[i] <= '9') {
                temp = temp * 10 + s[i] - '0';
            } else if (s[i] == ' ' || s[i] == '\0') {
                a[n] = temp;
                sum += temp;
                n++;
                temp = 0;
            } else break;
        }
        if (i <= s.length()) printf("ERROR\n");
        else {
            for (int j = 1; j < n; j++) {
                for (int v = sum / 2; v >= a[j]; v--) {
                    dp[v] = max(dp[v], dp[v - a[j]] + a[j]);
                }
            }
            sum1 = dp[sum / 2];
            long long sum2 = sum - sum1;
            if (sum1 > sum2) printf("%lld %lld\n", sum1, sum2);
            else printf("%lld %lld\n", sum2, sum1);
        }
    }
    return 0;
}

代码(网上的——AC)

#include <iostream>
#include <cstdio>
#include <map>
#include <vector>
#include <string>
#include <memory.h>
#include <set>
#include <stack>
#include <queue>
#include <unordered_map>
#include <iomanip>
#include <algorithm>
#include <cmath>
using namespace std;
bool judge(string s) {
	for (int i = 0; i < s.length(); i++) {
		if (!isdigit(s[i]))
			return false;
	}
	return true;
}
void dfs(int index, int ans, int half, int num[], int count, int &res) {
	if (index == count)
		return;
	if (ans + num[index] <= half) {
		res = max(res, ans + num[index]);//res每次存小于等于half且最接近half的子数组和。
		if (res == half)//等于half最优直接返回
			return;
		dfs(index + 1, ans + num[index], half, num, count, res);//如果选了第index个数,和还小于half继续dfs
	}
	dfs(index + 1, ans, half, num, count, res);//不选第index个
}

int main() {
	string s;
	while (getline(cin, s)) {
		//处理字符串,存入数组之中。
		int pre = 0, number[1010], count = 0, sum = 0;
		bool flag = true;
		for (int i = 0; i < s.length(); i++) {
			if (s[i] == ' ') {
				string str = s.substr(pre, i - pre);
				if (judge(str)) {
					int temp = stoi(str);
					sum += temp;
					number[count++] = temp;
					pre = i + 1;
				}
				else {
					flag = false;
					break;
				}
			}
		}
		string str = s.substr(pre, s.length() - pre);//判断最后一个字符串
		if (judge(str)) {
			int temp = stoi(str);
			sum += temp;
			number[count++] = temp;
		}
		else
			flag = false;

		//寻找最接近sum/2的子数组。
		if (flag) {
			int half = sum / 2, res = 0;
			dfs(0, 0, half, number, count, res);
			cout << sum - res << " " << res << endl;
		}
		else {
			cout << "ERROR" << endl;
		}
	}
	return 0;
}

B 拦截导弹

image-20200903020331886

题目解析

一开始没发现这道题是最长不上升子序列的DP题目,直接按照自己的想法做,自然WA了🙈,错误原因是认为能拦截的导弹是连续的,下一个导弹若不能拦截,则重新开始计数。最后统计最大值。

错误代码

#include <cstdio>
#define maxn 25
int main() {
    int n, a[maxn];
    while (scanf("%d", &n) && n) {
        for (int i = 0; i < n; i++) {
            scanf("%d", &a[i]);
        }
        int temp = a[0], t_sum = 1, max_sum = 0;
        for (int i = 1; i < n; i++) {
            if (a[i] <= temp) {
                t_sum++;
            } else {
                if (t_sum > max_sum) max_sum = t_sum;
                t_sum = 1; //从当前这个比temp大的数算起
            }
            temp = a[i];
        }
        if (t_sum > max_sum) max_sum = t_sum; //处理最后一个t_sum
        printf("%d\n", max_sum);
    }
    return 0;
}

正确代码

//是最长不上升子序列的问题
#include <cstdio>
#define maxn 25
int main() {
    int n, a[maxn], dp[maxn]; //dp[i] 表示以a[i]结尾的最长不下降子序列长度
    while (scanf("%d", &n) && n) {
        for (int i = 0; i < n; i++) scanf("%d", &a[i]);
        int ans = 0;
        for (int i = 0; i < n; i++) {
            dp[i] = 1;
            for (int j = 0; j < i; j++) {
                if (a[j] >= a[i] && dp[i] < dp[j] + 1) {
                    dp[i] = dp[j] + 1;
                }
            }
            if (dp[i] > ans) ans = dp[i];
        }
        printf("%d\n", ans);
    }
    return 0;
}

C 合唱队形 🌟

image-20200903020748404

题目解析

遍历输入的同学数组,计算每一个位置对应的最长上升子序列数目、最长下降子序列数目,假设二者和为temp,则每次对应要出列的学生数目= sum-temp+1

Why加一?
因为对Ti,计算[T1, Ti ]的最长上升子序列数目,计算[Ti, Tn]的最长下降子序列数目,Ti会被重复计算,所以sum-temp多减了一个Ti,要加回来。

代码

#include <cstdio>
#include <algorithm>
#define maxn 100
using namespace std;

int n, a[maxn], dp[maxn];

//求 [0, mid] 的最长上升子序列
int increase(int mid) {
    int ans = 0;
    fill(dp, dp + maxn, 1);
    for (int i = 0; i <= mid; i++) {
        for (int j = 0; j < i; j++) {
            if (a[j] < a[i] && dp[i] < dp[j] + 1) {
                dp[i] = dp[j] + 1;
            }
        }
        if (dp[i] > ans) ans = dp[i];
    }
    return ans;
}

//求 [mid, n) 的最长下降子序列
int decrease(int mid) {
    int ans = 0;
    fill(dp, dp + maxn, 1);
    for (int i = mid; i < n; i++) {
        for (int j = mid; j < i; j++) {
            if (a[j] > a[i] && dp[i] < dp[j] + 1) {
                dp[i] = dp[j] + 1;
            }
        }
        if (dp[i] > ans) ans = dp[i];
    }
    return ans;
}

int main() {
    while (scanf("%d", &n) && n) {
        for (int i = 0; i < n; i++) scanf("%d", &a[i]);
        int sum = 0, temp;
        for (int i = 0; i < n; i++) {
            temp = increase(i) + decrease(i);
            if (temp > sum) sum = temp;
        }
        printf("%d\n", n - sum + 1); //因为中间的Ti被重复计算,所以要加上去
    }
    return 0;
}

D: Coincidence

image-20200903021359064

题目解析

标准最长上升子序列问题

代码

//最长公共子序列
#include <cstdio>
#include <cstring>
#include <algorithm>

#define maxn 105
using namespace std;

int main() {
    char a[maxn], b[maxn];
    int dp[maxn][maxn]; //dp[i][j]表示a的前i个字符与b的前j个字符的最长公共子序列的长度
    while (gets(a + 1) && gets(b + 1)) {
        int lena = strlen(a + 1);
        int lenb = strlen(b + 1);
        for (int i = 0; i <= lena; i++) dp[i][0] = 0;
        for (int i = 0; i <= lenb; i++) dp[0][i] = 0;
        for (int i = 1; i <= lena; i++) {
            for (int j = 1; j <= lenb; j++) {
                if (a[i] == b[j]) dp[i][j] = dp[i - 1][j - 1] + 1;
                else dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
            }
        }
        printf("%d\n",dp[lena][lenb]);
    }
    return 0;
}

E 最大子矩阵 🌟🌟

image-20200903021547619

题目解析

这道题是一个二维的最大连续子序列和问题【完全没想到orz】,需降至一维进行求解
对于确定的从第 A 行到 第 B 行,将同一列的数相加,最终得到一个一维的数序列,对于这个数序列求最大连续子序列即可🙈

状态转移方程为:dp[i] = max(dp[i - 1] + A[i], A[i])

⚠️ 注意ans的初值(看代码)

代码

//二维的最大连续子序列和
#include <cstdio>
#include <algorithm>
#include <cstring>

#define maxn 105
using namespace std;
int n, dp[maxn]; //dp[i]以A[i]结尾的最大连续子序列和

int sequence(int *A) {
    dp[0] = A[0];
    int ans = dp[0]; // i从1开始否则dp[i-1]越界,ans初始化为dp[0],否则若只有A[0]时不进行for循环,ans就得不到值了
    for (int i = 1; i < n; i++) {
        dp[i] = max(dp[i - 1] + A[i], A[i]);
        if (dp[i] > ans) ans = dp[i];
    }
    return ans;
}

int main() {
    int a[maxn][maxn];
    while (scanf("%d", &n) != EOF) {
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                scanf("%d", &a[i][j]);
            }
        }
        int t[maxn];
        int ans = a[0][0], temp; //!!!有可能输入的都是负数,所以不能将ans初始化为0
        for (int i = 0; i < n; i++) {
            memset(t, 0, sizeof(t));
            for (int j = i; j < n; j++) {
                for (int k = 0; k < n; k++) {
                    t[k] += a[j][k];
                }
                temp = sequence(t);
                if (temp > ans) ans = temp;
            }
        }
        printf("%d\n",ans);
    }
    return 0;
}

F 放苹果 🌟🌟🌟

image-20200903022028745

题目解析

所有不同的摆放方法可以分为两类:至少有一个盘子空着和所有的盘子都不空。
我们可以分别计算这两类摆放方法的数目,然后把它们加起来。
1️⃣ 至少空一个盘子:N 个盘子摆放 M 个苹果的摆放数目 = N-1 个盘子摆放 M 个苹果的摆放方法数目
2️⃣所有盘子都不空:即N个盘子中至少有1个苹果,则 N个盘子摆放 M 个苹果的摆放方法数目 = N 个盘子摆放 M-N 个苹果的摆放方法数目
我们可以据此来用递归的方法求解这个问题。

设f(m,n)为m个苹果,n个盘子的放法数目,则先对n作讨论:

  1. 若 n>m,必定至少有 n-m 个盘子永远空着,去掉它们对摆放苹果放法数目不产生影响 if(n>m) f(m,n)=f(m,m)
  2. 若 n<=m,不同的方法可以分为两类👇
    • 有至少一个盘子空着:f(m,n)=f(m,n-1)
    • 所有盘子都有苹果,则可以从每个盘子中拿掉一个苹果,不影响不同放法的数目f(m,n)=f(m-n,n)
    • 总的放苹果的放法数目等于两者的和,即f(m,n)=f(m,n-1)+ f(m-n,n)
  3. 边界:f(m,n)=f(m,n-1) 时 n 不断变小,f(m,n)=f(m-n,n)时 m 不断变小,因为n>m时return f(m,m)终会到达出口m==0
    • n=1时,所有苹果都必须放到一个盘子里,所有返回1
    • m=0时,当没有苹果可放时,定义为1种放法(空盘)

PS:这个blog是各种情况的总结,我没自己看orz,有时间研究下:
https://www.cnblogs.com/celia01/archive/2012/02/19/2358673.html

代码

#include <cstdio>

// m个苹果,n个盘子
int f(int m, int n) {
    if (m == 0 || n == 1) return 1; //没有苹果/只有一个盘子都只有一种放法:空盘子/放这个盘子里
    if (m < n) return f(m, m); //盘子比苹果多,则至少有n-m个空盘子,所以接下来需要在m个盘子里放m个苹果
    else return f(m - n, n) + f(m, n - 1); //苹果比盘子多,则可以不空盘子,在当前所有盘子里放一个苹果/空一个盘子
}

int main() {
    int t;
    int m, n;
    scanf("%d", &t);
    while (t--) {
        scanf("%d%d", &m, &n);
        printf("%d\n", f(m, n));
    }
    return 0;
}

G 点菜问题

image-20200903022224149

题目解析

标准01背包题

代码

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define maxn 105
#define maxv 1005

int main() {
    int V, n;
    int w[maxn], c[maxn], dp[maxv];
    while (scanf("%d%d", &V, &n) != EOF) {
        for (int i = 1; i <= n; i++) {
            scanf("%d%d", &w[i], &c[i]);
        }
        memset(dp, 0, sizeof(dp));
        for (int i = 1; i <= n; i++) {
            for (int v = V; v >= w[i]; v--) {
                dp[v] = max(dp[v], dp[v - w[i]] + c[i]);
            }
        }
        printf("%d\n",dp[V]);
    }
    return 0;
}

H 最大包销额 ❓

image-20200903022345915

题目解析

首先判断每张发票的有效性,若有效则将这张发票的金额和*100(化为整数)加入需计算01背包的数组w
⚠️ 不能将有效发票的每个物品金额*100加入w数组,因为每张发票要报销只能一整张包销

01背包的容量是报销额度*100

🔞但最后还是不知道为啥错误50 🙈🙃吐血了

错误代码

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define maxn 35
#define maxv 200000

int main() {
    double q, temp[30];
    int n, m, V, w[maxn], dp[maxv];
    while (scanf("%lf%d", &q, &n) && n) {
        memset(dp, 0, sizeof(dp));
        V = int(q * 100);
        int lenw = 1;

        for (int i = 0; i < n; i++) {
            scanf("%d", &m);
            double sum = 0;
            char t;
            int j;
            for (j = 0; j < m; j++) {
                scanf(" %c:%lf", &t, &temp[j]);
                if (temp[j] > 600) break;
                if (t == 'A' || t == 'B' || t == 'C') sum += temp[j];
                else break;
            }
            if (j < m || sum > 1000) continue;
            w[lenw++] = int(sum * 100);
        }
        for (int i = 1; i <= lenw; i++) {
            for (int v = V; v >= w[i]; v--) {
                dp[v] = max(dp[v], dp[v - w[i]] + w[i]);
            }
        }
        printf("%.2lf\n", dp[V] / 100.0);
    }
    return 0;
}

I 毕业bg

image-20200903022503520

题目解析

网上说是01背包题。。。完全没看出来_(:з」∠)_

发起人必须在几小时后离开,这就相当于背包的体积,即在这几个小时里,可以安排多少场bg,以获得最大的快乐度,然后持续时间,相当于一个物品的容积,快乐度相当于物品的价值

⚠️

  1. 要对所有bg按照发起人离开时间 t 从小到大进行排序,保证每次新加进来一个bg后可以保证这段时间内前面所有bg都能完成。
  2. 每次当前dp[i][v]时的背包容量是dp[i].t,不是V
  3. 最后不能直接输出dp[V],要遍历多有的dp[i],因为每次的背包容量不一定相同

代码

#include <cstdio>
#include <algorithm>
#include <cstring>
#define maxn 30
#define maxv 200
using namespace std;
struct BG {
    int h, l, t;
} bg[maxn];

bool cmp(BG a, BG b) {
    return a.t < b.t;
}

int main() {
    int n;
    int dp[maxv];
    while (scanf("%d", &n) && n != -1) {
        memset(dp, 0, sizeof(dp));
        for (int i = 1; i <= n; i++) {
            scanf("%d%d%d", &bg[i].h, &bg[i].l, &bg[i].t);
        }
        sort(bg + 1, bg + n + 1, cmp);
        int V = bg[n].t;
        for (int i = 1; i <= n; i++) {
            for (int v = bg[i].t; v >= bg[i].l; v--) { //前i个活动的V是bg[i].t
                dp[v] = max(dp[v], dp[v - bg[i].l] + bg[i].h);
            }
        }
        //!!! 不能直接输出dp[V]
        int ans = 0;
        for (int i = V; i >= 0; i--) {
            if (dp[i] > ans) ans = dp[i];
        }
        printf("%d\n", ans);
    }
    return 0;
}

本文作者:Joey-Wang

本文链接:https://www.cnblogs.com/joey-wang/p/14541197.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   Joey-Wang  阅读(54)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
展开