当前位置:   article > 正文

《算法笔记》11.8小节——动态规划专题->总结_《算法笔记》11.8小节——问题 a: 第二题

《算法笔记》11.8小节——问题 a: 第二题

问题 A: 第二题

问题描述:一个数组中有若干正整数,将此数组划分为两个子数组,使得两个子数组各元素之和a,b的差最小,对于非法输入应该输出ERROR。

  • 输入
数组中的元素
  • 1
  • 输出
降序输出两个子数组的元素和
  • 1
  • 样例输入
10 20 30 10 10
10 20 abc 10 10
  • 1
  • 2
  • 样例输出
40 40
ERROR
  • 1
  • 2

01背包的想法

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<stdio.h>
#include<vector>
#include<string>
#include<cstdlib>
using namespace std;
const int maxn = 50005;
const int INF = 1000000000;
int A[maxn], dp[maxn];
char str[maxn];
int num;
int strToA()
{
	int len = strlen(str);
	num = 0;
	int temp;
	for (int i = 0; i < len; )
	{
		temp = 1;
		if (str[i] == ' ')
			i++;
		else if (str[i] >= '0' && str[i] <= '9')
		{
			while (str[i] != ' '&&i<len)
			{
				A[num] = A[num] * 10 + (str[i] - '0');
				i++;
			}
			num++;
		}
		else
			return 0;

	}
	return num;
}
int main()
{
	while (1)
	{
		gets_s(str);
		memset(A, 0, sizeof(A));
		if (strToA())//说明是正常的
		{
			memset(dp, 0, sizeof(dp));
			//下面进行动态规划,01背包问题,分出来一个数组让数组的元素和接近sum/2
			int sum = 0;			
			for (int i = 0; i < num; i++)
			{
				sum += A[i];
			}
			//dp[v]表示和恰好为v的背包价值
			for (int i = 0; i < num; i++)
			{
				for (int j = sum / 2; j >= A[i]; j--)
				{
					dp[j] = max(dp[j],dp[j-A[i]]+A[i]);
				}
			}
			int max = 0;
			for (int j = 0; j <= sum / 2; j++)
			{
				if (dp[j] > max)
					max = dp[j];
			}
			
			printf("%d %d\n",max,sum-max);
		}
		else//输出ERROR
		{
			printf("ERROR\n");
		}
	}
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78

DFS的想法:

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<stdio.h>
#include<vector>
#include<string>
#include<cstdlib>
using namespace std;
const int maxn = 50005;
const int INF = 1000000000;
int A[maxn], dp[maxn];
char str[maxn];
int num;//整数数组的大小
int sum, ans;
int half;
int strToA()
{
	int len = strlen(str);
	num = 0;
	int temp;
	for (int i = 0; i < len; )
	{
		temp = 1;
		if (str[i] == ' ')
			i++;
		else if (str[i] >= '0' && str[i] <= '9')
		{
			while (str[i] != ' '&&i<len)
			{
				A[num] = A[num] * 10 + (str[i] - '0');
				i++;
			}
			num++;
		}
		else
			return 0;

	}
	return num;
}

void dfs(int i, int sum)//i是当前正在比较的节点A[i],sum是当前的总和
{
	if (i >= num ) return;
	//if (i >= num || sum + (num - i) * A[i] < ans) return;//剪枝啊剪枝。
//sum+(n-i)*a[i]<ans这个剪枝意思是:因为我a数组是从大到小排序,接下来啊a[i]...a[n-1]中任意一个最大也是a[i],
//那么如果我当前子序列和sum加上剩下的所有n-i个数最大也是sum+(n-i)*a[i],如果这个数还<当前暂存在ans中的子序列和,
//那么不需要继续往下加了,因为不可能得到比当前ans大的数。
//这个剪枝非常巧妙,且随着ans的更新会变得更为有效。我之前并没有加这个剪枝,结果超时了(当然,也可能是之前写错了)
	//!!选择A[i]
	if (sum + A[i] <= half) {//只有不超过half,当前数a[i]才可能被加
		ans = max(ans, sum + A[i]);//只有新加入a[i],ans才可能被更新
		dfs(i + 1, sum + A[i]);
	}
	//!!不选择A[i]
	dfs(i + 1, sum);

}
bool cmp(int a, int b)
{
	return a > b;
}
int main()
{
	while (1)
	{
		gets_s(str);
		memset(A, 0, sizeof(A));
		if (strToA())//说明是正常的
		{
			//将数组从大到小进行排序
			sort(A,A+num,cmp);
			//计算sum/2
			sum = 0;
			for (int i = 0; i < num; i++)
			{
				sum += A[i];
			}
			half = sum / 2;
			//最终结果为ans,全局变量ans初始化
			ans = 0;
			dfs(0, 0);
			printf("%d %d\n",sum-ans,ans);
		}
		else//输出ERROR
		{
			printf("ERROR\n");
		}
	}
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92

问题 B: 拦截导弹

问题描述:某国为了防御敌国的导弹袭击,开发出一种导弹拦截系统。但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。某天,雷达捕捉到敌国的导弹来袭,并观测到导弹依次飞来的高度,请计算这套系统最多能拦截多少导弹。拦截来袭导弹时,必须按来袭导弹袭击的时间顺序,不允许先拦截后面的导弹,再拦截前面的导弹。
输入:每组输入有两行,第一行,输入雷达捕捉到的敌国导弹的数量k(k<=25),第二行,输入k个正整数,表示k枚导弹的高度,按来袭导弹的袭击时间顺序给出,以空格分隔。
输出:每组输出只有一行,包含一个整数,表示最多能拦截多少枚导弹。
样例输入

4
9 6 7 8
7
4 5 6 7 13 42 3
5
6 5 4 3 5
0
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

样例输出

2
2
4
  • 1
  • 2
  • 3

最长不下降子序列长度,这题变成了最长不上升子序列,变个符号就可以了

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<cmath>
#include<algorithm>
using namespace std;
const int maxn = 30;
int dp[maxn];
int A[maxn];
int n;
int main() {
	while (scanf("%d", &n) != EOF)
	{
		if (n == 0)
			break;
		for (int i = 1; i <= n; i++)
		{
			scanf("%d", &A[i]);
		}
		int ans = -1;
		for (int i = 1; i <= n; i++)
		{
			dp[i] = 1;
			for (int j = 1; j < i; j++)
			{
				if (A[j] >= A[i] && (dp[j] + 1 > dp[i]))
					dp[i] = dp[j] + 1;
			}
			ans = max(ans, dp[i]);
		}
		printf("%d\n", ans);

	}
	return 0;

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

问题 C: 合唱队形

问题描述:N位同学站成一排,音乐老师要请其中的(N-K)位同学出列,使得剩下的K位同学不交换位置就能排成合唱队形。
合唱队形是指这样的一种队形:设K位同学从左到右依次编号为1, 2, …, K,他们的身高分别为T1, T2, …, TK,
则他们的身高满足T1 < T2 < … < Ti , Ti > Ti+1 > … > TK (1 <= i <= K)。
你的任务是,已知所有N位同学的身高,计算最少需要几位同学出列,可以使得剩下的同学排成合唱队形。
输入:输入的第一行是一个整数N(2 <= N <= 100),表示同学的总数。
第一行有n个整数,用空格分隔,第i个整数Ti(130 <= Ti <= 230)是第i位同学的身高(厘米)。
输出:输入的第一行是一个整数N(2 <= N <= 100),表示同学的总数。
第一行有n个整数,用空格分隔,第i个整数Ti(130 <= Ti <= 230)是第i位同学的身高(厘米)。
样例输入

3
174 208 219 
6
145 206 193 171 187 167 
0
  • 1
  • 2
  • 3
  • 4
  • 5

样例输出

0
1
  • 1
  • 2

两个动态数组,一个算最长上升子序列,一个算最长下降子序列即可

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<cmath>
#include<algorithm>
using namespace std;
const int maxn = 110;
int dp1[maxn];//从前向后数,记录以A[i]结尾的最长上升子序列长度
int dp2[maxn];//从后向前数,记录以A[i]结尾的最长下降子序列长度
int A[maxn];
int n;
int main() {
	while (scanf("%d", &n) != EOF)
	{
		if (n == 0)
			break;
		for (int i = 1; i <= n; i++)
		{
			scanf("%d", &A[i]);
		}
		int ans = -1;
		//计算dp1[]
		for (int i = 1; i <= n; i++)
		{
			dp1[i] = 1;
			for (int j = 1; j < i; j++)
			{
				if (A[j] < A[i] && (dp1[j] + 1 > dp1[i]))
					dp1[i] = dp1[j] + 1;
			}
			
		}
		//计算dp2[],dp2[i]与大于i的j有关
		for (int i = n; i >=1; i--)
		{
			dp2[i] = 1;
			for (int j = n; j >i; j--)
			{
				if (A[j] < A[i] && (dp2[j] + 1 > dp2[i]))
					dp2[i] = dp2[j] + 1;
			}
		}
		for (int i = 1; i <= n; i++)
		{
			ans = max(ans,dp1[i]+dp2[i]);
		}		
		printf("%d\n", n-ans+1);
	}
	return 0;

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51

问题 D: Coincidence

问题描述:Find a longest common subsequence of two strings.
输入:First and second line of each input case contain two strings of lowercase character a…z. There are no spaces before, inside or after the strings. Lengths of strings do not exceed 100.
输出:For each case, output k – the length of a longest common subsequence in one line.
样例输入

google
goodbye
  • 1
  • 2

样例输出

4
  • 1

最长公共子序列

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn = 110;
int dp[maxn][maxn];
char A[maxn], B[maxn];
int main() {
	while (scanf("%s %s", A + 1, B + 1) != EOF)
	{
		int lenA = strlen(A + 1);
		int lenB = strlen(B + 1);
		for (int i = 0; i <= lenA; i++)
		{
			dp[i][0] = 0;
		}
		for (int j = 0; j <= lenB; j++)
		{
			dp[0][j] = 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;
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40

问题 E: 最大子矩阵

问题描述:已知矩阵的大小定义为矩阵中所有元素的和。给定一个矩阵,你的任务是找到最大的非空(大小至少是1 * 1)子矩阵。
比如,如下4 * 4的矩阵

0 -2 -7 0
9 2 -6 2
-4 1 -4 1
-1 8 0 -2

的最大子矩阵是

9 2
-4 1
-1 8

这个子矩阵的大小是15。
输入:输入是一个N * N的矩阵。输入的第一行给出N (0 < N <= 100)。
再后面的若干行中,依次(首先从左到右给出第一行的N个整数,再从左到右给出第二行的N个整数……)给出矩阵中的N2个整数,整数之间由空白字符分隔(空格或者空行)。
已知矩阵中整数的范围都在[-127, 127]。
输出:测试数据可能有多组,对于每组测试数据,输出最大子矩阵的大小。

  • 样例输入
1
27 
3
-40 29 -16 
38 18 22 
24 -35 5 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 样例输出
27
78
  • 1
  • 2

参考网址:
https://blog.csdn.net/mirocky/article/details/104158753
最大连续子序列和的变形
一维变成了二维:
使用Memset会提示错误,以后就用fill啦

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<stdio.h>
#include<vector>
using namespace std;
const int maxn = 110;
int M[maxn][maxn];
int temp[maxn];
int dp[maxn];
int n;
int maxtemp()
{
	int ans = temp[0];
	dp[0] = temp[0];
	for (int i = 1; i < n; i++)
	{
		dp[i] = max(temp[i],dp[i-1]+temp[i]);
		ans = max(ans,dp[i]);
	}
	return ans;
}
int main() {

	while (scanf("%d", &n) != EOF)
	{
		for (int i = 0; i < n; i++)
		{
			for (int j = 0; j < n; j++)
			{
				scanf("%d",&M[i][j]);
			}
		}
		//初始化完毕
		int ans = M[0][0];
		for (int i = 0; i < n; i++)//一次计算0到n-1行
		{
			fill(temp, temp+maxn,0);
			for (int j = i; j < n; j++)
			{
				for (int k = 0; k < n; k++)
				{
					temp[k] += M[j][k];
				}
				ans = max(ans, maxtemp());
			}			
		}
		printf("%d\n",ans);
	}
	return 0;
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53

问题 F: 放苹果

问题描述:把M个同样的苹果放在N个同样的盘子里,允许有的盘子空着不放,问共有多少种不同的分法?(用K表示)5,1,1和1,5,1 是同一种分法。
输入:第一行是测试数据的数目t(0 <= t <= 20)。以下每行均包含二个整数M和N,以空格分开。1<=M,N<=10。
输出:对输入的每组数据M和N,用一行输出相应的K。

  • 样例输入
2
6 3
7 2
  • 1
  • 2
  • 3
  • 样例输出
7
4
  • 1
  • 2

提示:解题分析:
设f(m,n) 为m个苹果,n个盘子的放法数目,则先对n作讨论,
当n>m:必定有n-m个盘子永远空着,去掉它们对摆放苹果方法数目不产生影响。即if(n>m) f(m,n) = f(m,m)  
当n<=m:不同的放法可以分成两类:
1、有至少一个盘子空着,即相当于f(m,n) = f(m,n-1);
2、所有盘子都有苹果,相当于可以从每个盘子中拿掉一个苹果,不影响不同放法的数目,即f(m,n) = f(m-n,n).
而总的放苹果的放法数目等于两者的和,即 f(m,n) =f(m,n-1)+f(m-n,n)
递归出口条件说明:
当n=1时,所有苹果都必须放在一个盘子里,所以返回1;
当没有苹果可放时,定义为1种放法;
递归的两条路,第一条n会逐渐减少,终会到达出口n1;
第二条m会逐渐减少,因为n>m时,我们会return f(m,m) 所以终会到达出口m
0.
什么时候我能有提示这么清晰的思路啊!膜拜!
DFS方法

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<stdio.h>
#include<vector>
using namespace std;
int m, n;//m个苹果,n个盘子
int f(int m,int n)
{
	if (n == 1 || m == 0) return 1;
	else if (n > m) return f(m, m);
	else return f(m, n - 1) + f(m - n, n);
}
int main() {
	int t;
	scanf("%d",&t);
	for (int i = 0; i < t; i++)
	{
		scanf("%d %d", &m, &n);
		printf("%d\n", f(m, n));
	}	
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

动态规划方法

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<stdio.h>
#include<vector>
using namespace std;
const int maxn = 20;
int dp[maxn][maxn];
int m, n;//m个苹果,n个盘子
int f(int m,int n)
{
	if (n == 1 || m == 0) return 1;
	else if (n > m) return f(m, m);
	else return f(m, n - 1) + f(m - n, n);
}
int main() {
	int t;
	scanf("%d",&t);
	for (int i = 0; i < t; i++)
	{
		//初始化:
		scanf("%d %d", &m, &n);		
		fill(dp[0], dp[0] + maxn * maxn ,0);
		for (int j = 0; j <= n; j++)
			dp[0][j] = 1;
		for (int j = 0; j <= m; j++)
		{
			dp[j][1] = 1;
			dp[j][0] = 0;
		}
			
		for (int j = 1; j <= m; j++)
		{
			for (int i = 1; i <= n; i++)
			{
				if (i > j) dp[j][i] = dp[j][j];
				else dp[j][i] = dp[j][i-1] + dp[j-i][i];
			}
		}
		printf("%d\n",dp[m][n]);
	}	
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44

问题 G: 点菜问题

问题描述:北大网络实验室经常有活动需要叫外买,但是每次叫外买的报销经费的总额最大为C元,有N种菜可以点,经过长时间的点菜,网络实验室对于每种菜i都有一个量化的评价分数(表示这个菜可口程度),为Vi,每种菜的价格为Pi, 问如何选择各种菜,使得在报销额度范围内能使点到的菜的总评价分数最大。
注意:由于需要营养多样化,每种菜只能点一次。
-输入:输入的第一行有两个整数C(1 <= C <= 1000)和N(1 <= N <= 100),C代表总共能够报销的额度,N>代表能点菜的数目。接下来的N行每行包括两个在1到100之间(包括1和100)的的整数,分别表示菜的>价格和菜的评价分数。

输出:输出只包括一行,这一行只包含一个整数,表示在报销额度范围内,所点的菜得到的最大评价分数。

  • 样例输入
1 3
1 5
3 3
2 5
24 8
2 9
8 6
4 1
1 4
2 2
10 5
2 1
1 4
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 样例输出
5
30
  • 1
  • 2

01背包问题

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<stdio.h>
#include<vector>
using namespace std;
//物品的重量是w,价值也是w
const int maxn = 20005;
int w[maxn], c[maxn], dp[maxn];
int n;
int V;
int main() {

	while (scanf("%d", &V) != EOF)
	{
		scanf("%d", &n);//n件物品
		int ans = 0;
		for (int i = 1; i <= n; i++)
		{
			scanf("%d", &w[i]);
			scanf("%d", &c[i]);
			
		}
		for (int v = 0; v <= V; v++)
		{
			dp[v] = 0;
		}
		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]);
			}
		}
		int max = 0;
		for (int v = 0; v <= V; v++)
		{
			if (dp[v] > max)
			{
				max = dp[v];
			}
		}
		printf("%d\n",max);
	}
	return 0;
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48

问题 H: 最大报销额

问题描述:现有一笔经费可以报销一定额度的发票。允许报销的发票类型包括买图书(A类)、文具(B类)、差旅(C类),要求每张发票的总额不得超过1000元,每张发票上,单项物品的价值不得超过600元。现请你编写程序,在给出的一堆发票中找出可以报销的、不超过给定额度的最大报销额。

  • 输入
测试输入包含若干测试用例。每个测试用例的第1行包含两个正数 Q 和 N,其中 Q(Q<=2000) 是给定的报销额度,N(N<=30)是发票张数。随后是 N 行输入,每行的格式为:
    m Type_1:price_1 Type_2:price_2 ... Type_m:price_m
其中正整数 m 是这张发票上所开物品的件数,Type_i 和 price_i 是第 i 项物品的种类和价值。物品种类用一个大写英文字母表示。当N为0时,全部输入结束,相应的结果不要输出。
  • 1
  • 2
  • 3
  • 输出
对每个测试用例输出1行,即可以报销的最大数额,精确到小数点后2位。
  • 1
  • 样例输入
300.00 3
2 A:33.50 B:150.00
1 C:850.00
3 A:159.99 A:350.00 X:10.00
1100.00 2
2 B:600.00 A:400.00
1 C:300.50
150.00 0
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 样例输出
183.50
1000.00
  • 1
  • 2

感觉这题重点在字符串的处理,老问题codeup编译错误
需要注意dp[v]表示容量恰好是v时的总价值,因为题目里有小数,而dp数组的下标肯定是整数,所以注意都乘以100来比较

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<stdio.h>
#include<vector>
using namespace std;
//物品的重量是w,价值也是w
const int maxn = 200010;
int w[maxn], dp[maxn];
int n;
int price[10];
int Price;
char fapiao[1000];
double V;
int num;//表示发票内物品个数num
void pre(int id)//预处理发票
{	
	int temp;
	int sum = 0;
	int flag = 0;//==0表示合理,否则表示不合理
	int len = strlen(fapiao);
	for (int i = 0; i < len; i++)
	{
		
		if (fapiao[i] == ':')
		{		
			temp = 0;			
			i++;
			while (fapiao[i] != ' '&&i<len)
			{
				if (fapiao[i] == '.')
					i++;
				else
				{
					price[temp] = fapiao[i] - '0';					
					temp++;
					i++;
				}
			}				
			//判断价格是否合理
			Price = 0;
			for (int j = 0; j < temp; j++)
			{
				Price = Price * 10 + price[j];				
			}
			sum += Price;			
			if (Price > 60000)
			{
				flag = 1;
				break;
			}
		}
			
	}
	//判断总价是否合理;
	if (sum > 100000)
		flag = 1;
	if (flag == 1)//表示这张发票不合格
		w[id]=0;
	else
	{
		w[id]=sum;
	}	
}
int main() {

	while (scanf("%lf %d", &V,&n) != EOF)
	{
		if (n == 0)
			break;
		V *= 100;	
		getchar();
		for (int i = 1; i <= n; i++)//处理发票1-n
		{					
			gets_s(fapiao);			
			pre(i);		//预处理	
		}
	/*	for (int i = 1; i <= n; i++)
		{
			printf("%d\n", w[i]);
		}*/
		
		//至此预处理完毕,下面解决01背包问题,
		for (int v = 0; v <= V; v++)
		{
			dp[v] = 0;
		}
		for (int i = 1; i <= n; i++)
		{
			for (int v = V; v >= w[i]; v--)
			{
				dp[v] = max(dp[v], dp[v - w[i]] + w[i]);
			}
		}
		int max = 0;
		for (int v = 0; v <= V; v++)
		{
			if (dp[v] > max)
			{
				max = dp[v];
			}
		}
		double max1 = (double)max * 1.0 / 100;
		printf("%.2f\n", max1);
		
	}
	return 0;
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110

问题 I: 毕业bg

问题描述:每 年毕业的季节都会有大量毕业生发起狂欢,好朋友们相约吃散伙饭,网络上称为“bg”。参加不同团体的bg会有不同的感觉,我们可以用一个非负整数为每个 bg定义一个“快乐度”。现给定一个bg列表,上面列出每个bg的快乐度、持续长度、bg发起人的离校时间,请你安排一系列bg的时间使得自己可以获得最 大的快乐度。

例如有4场bg:
第1场快乐度为5,持续1小时,发起人必须在1小时后离开;
第2场快乐度为10,持续2小时,发起人必须在3小时后离开;
第3场快乐度为6,持续1小时,发起人必须在2小时后离开;
第4场快乐度为3,持续1小时,发起人必须在1小时后离开。
则获得最大快乐度的安排应该是:先开始第3场,获得快乐度6,在第1小时结束,发起人也来得及离开;再开始第2场,获得快乐度10,在第3小时结束,发起人正好来得及离开。此时已经无法再安排其他的bg,因为发起人都已经离开了学校。因此获得的最大快乐度为16。

注意bg必须在发起人离开前结束,你不可以中途离开一场bg,也不可以中途加入一场bg。
又因为你的人缘太好,可能有多达30个团体bg你,所以你需要写个程序来解决这个时间安排的问题。

  • 输入
测试输入包含若干测试用例。每个测试用例的第1行包含一个整数N (<=30),随后有N行,每行给出一场bg的信息:
    h l t
其中 h 是快乐度,l是持续时间(小时),t是发起人离校时间。数据保证l不大于t,因为若发起人必须在t小时后离开,bg必须在主人离开前结束。

当N为负数时输入结束。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 输出
每个测试用例的输出占一行,输出最大快乐度。
  • 1
  • 样例输入
3
6 3 3
3 2 2
4 1 3
4
5 1 1
10 2 3
6 1 2
3 1 1
-1
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 样例输出
7
16
  • 1
  • 2

一开始以为是DAG最长路,因为感觉题目给的信息可以直接求出来ve和vl,然后因为要有向,题目说可以任意选,所以在每个节点之间都画上双向箭头,然后就没有思路了
看dl的题解发现这是01背包问题!?我晕了
快乐度:物品价值
最大的离开时间:背包容量
结束时间bg[i].t容量,持续时间bg[i].l容量,最早为l时刻结束,最晚为t时刻结束,第二层循环容量从l-t在变化。
注意还要把区间排序,为了让最先结束的排在前面。
真的好难想呀,动态规划的路还很长。

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<stdio.h>
#include<vector>
using namespace std;
//物品的重量是w,价值也是w
const int maxn = 1000;
int dp[maxn];
struct BG
{
	int h, l, t;//快乐度(物品的价值),持续时间(物品的容积),结束时间
}bg[maxn];

bool cmp(const BG a, const BG b)
{
	return a.t < b.t;
}
int main() {
	int n;
	while (scanf("%d", &n) != EOF)
	{
		if (n < 0)
			break;
		int max1 = 0;//最后离开时间
		for (int i = 1; i <= n; i++)
		{
			scanf("%d %d %d", &bg[i].h, &bg[i].l, &bg[i].t);
			if (bg[i].t > max1)
				max1 = bg[i].t;
		}
		//将区间排序,按照右端点从小到大排序,最先结束的排在前面
		sort(bg + 1, bg + n + 1, cmp);
	
		fill(dp, dp + maxn, 0);
		for (int i = 1; i <= n; ++i)
		{
			for (int j = bg[i].t; j >= bg[i].l; --j)
			{
				dp[j] = max(dp[j], dp[j - bg[i].l] + bg[i].h);
			}
		}
		int result = dp[max1];
		for (int j = max1; j >= 1; --j)
			if (result < dp[j])
				result = dp[j];
		printf("%d\n", result);
	}	
	return 0;
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/你好赵伟/article/detail/837016
推荐阅读
相关标签
  

闽ICP备14008679号