冬训贪心专题(部分)

这篇博客涵盖了多个编程挑战,包括如何在最佳时机兑换货币以获取最大利润,股票交易中的两次买卖策略,如何删除整数序列中的数字以达到最小结果,以及解决活动安排、股票最大子段和、数列分段、定价策略、排队接水、货币系统和接水问题等问题的算法。这些题目涉及贪心、动态规划、排序和数学策略,展示了在不同场景下如何运用计算机科学解决问题。
摘要由CSDN通过智能技术生成

A - 外币兑换

 

蒜头君刚从美国回来,发现手上还有一些未用完的美金,于是想去银行兑换成人民币。可是听说最近人民币将会升值,并从金融机构得到了接下来十二个月可能的美元对人民币汇率,现在,蒜头君想要在接下来一年中把美金都兑换成人民币,请问最多能得到多少人民币?

输入格式

输入的第一行是一个实数 N(1.00≤N≤100.00),表示蒜头君现有的美金数量。

接下来一行,包含 12 个实数 ai​,表示接下来十二个月的美元对人民币汇率。

输出格式

输出一个小数 R,表示蒜头君最多能获得的人民币数量,结果保留两位小数。

Sample 1

InputOutput
46.91
6.31 6.32 6.61 6.65 5.55 5.63 6.82 6.42 6.40 5.62 6.78 5.60
319.93

 比较简单的题目,找出最大的汇率相乘即可

#include <iostream>
#include <cstdio>
using namespace std;
double a[12];
int main()
{
	int i;
	double ans,money,maxn=0;
	cin>>money;
	for (i=0;i<12;i++) {
		cin>>a[i];
		if (a[i]>maxn) {
			maxn=a[i];
		}
	}
	ans=maxn*money;
	printf("%.2f",ans);
	return 0;
}

 B - 删数问题

键盘输入一个高精度的正整数 n,去掉其中任意 k 个数字后剩下的数字按原左右次序将组成一个新的正整数。编程对给定的 n,k,寻找一种方案使得剩下的数字组成的新数最小。

输出应包括所去掉的数字的位置和组成的新的整数。

输入格式

第一行一个高精度整数 n,n 不超过 200 位。

第二行一个整数 k,k 小于 n 的位数,表示要删去的数字个数。

输出格式

一个合法的整数,表示最后剩下的最小数。

Sample 1

InputOutput
175438 
4
13

Sample 2

InputOutput
101 2
0

贪心思想,局部最优解最后满足最优解

当前一个数大于后面一个数时,即可让后面的数覆盖前面的数,这样就可使局部满足最小。

同样有一种情况就是所有的数都是前面的小于后面的,这样就选择删除后面的最大的数。

#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
typedef long long ll;
const int maxn=210;
char a[maxn];
int main()
{
	int m,n,i;
	cin>>a>>m;
	n=strlen(a);
	while (m--) {
		for (i=0;i<n-1;i++) {
			if (a[i]>a[i+1]) {
				for (int t=i;t<n-1;t++) {
					a[t]=a[t+1];
				}
				break;
			}
		}
		n--;//具有删除最后一个数的功能
	}
	int cont=0;
	while (cont<n-1) {
		if (a[cont]=='0') {
			cont++;
		} else {
			break;
		}
	}
	for (i=cont;i<n;i++) {
		cout<<a[i];
	}
	return 0;
}

 C - 股票买卖

最近越来越多的人都投身股市,阿福也有点心动了。谨记着“股市有风险,入市需谨慎”,阿福决定先来研究一下简化版的股票买卖问题。

假设阿福已经准确预测出了某只股票在未来 N 天的价格,他希望买卖两次,使得获得的利润最高。为了计算简单起见,利润的计算方式为卖出的价格减去买入的价格。

同一天可以进行多次买卖。但是在第一次买入之后,必须要先卖出,然后才可以第二次买入。

现在,阿福想知道他最多可以获得多少利润。

Input

输入的第一行是一个整数 T (T <= 50) ,表示一共有 T 组数据。
接下来的每组数据,第一行是一个整数 N (1 <= N <= 100, 000) ,表示一共有 N 天。第二行是 N 个被空格分开的整数,表示每天该股票的价格。该股票每天的价格的绝对值均不会超过 1,000,000 。

Output

对于每组数据,输出一行。该行包含一个整数,表示阿福能够获得的最大的利润。

Sample

InputOutput
3
7
5 14 -2 4 9 3 17
6
6 8 7 4 1 -2
4
18 9 5 2
28
2
0

Hint

对于第一组样例,阿福可以第 1 次在第 1 天买入(价格为 5 ),然后在第 2 天卖出(价格为 14 )。第 2 次在第 3 天买入(价格为 -2 ),然后在第 7 天卖出(价格为 17 )。一共获得的利润是 (14 - 5) + (17 - (-2)) = 28
对于第二组样例,阿福可以第 1 次在第 1 天买入(价格为 6 ),然后在第 2 天卖出(价格为 8 )。第 2 次仍然在第 2 天买入,然后在第 2 天卖出。一共获得的利润是 8 - 6 = 2
对于第三组样例,由于价格一直在下跌,阿福可以随便选择一天买入之后迅速卖出。获得的最大利润为 0

这里提供两种做法

1.贪心

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long ll;
const int maxn=1e5+10;
int mn[maxn],ma[maxn],dp1[maxn],dp2[maxn],a[maxn];
int main()
{
	int m;
	scanf("%d",&m);
	while (m--) {
		int n,i;
		scanf("%d",&n);
		for (i=1;i<=n;i++) {
			scanf("%d",&a[i]);
		}
		mn[1]=a[1];
		dp1[1]=0;
		for (i=2;i<=n;i++) {
			mn[i]=min(mn[i-1],a[i]);//在i天之前的价格的最小值
			dp1[i]=max(dp1[i-1],a[i]-mn[i]);//第i天的最大利润
		}
		dp2[n]=0;
		ma[n]=a[n];
		for (i=n-1;i>=1;i--) {
			ma[i]=max(ma[i+1],a[i]);//在i天之后的最大价格
			dp2[i]=max(dp2[i+1],ma[i]-a[i]);//第i天以后的最大利润
		}
		int ans=0;
		for (i=1;i<=n;i++) {
			if (ans<dp1[i]+dp2[i]) {
				ans=dp1[i]+dp2[i];
			}
		}
		printf("%d\n",ans);
	}
	return 0;
}

 2.动态规划

#include <iostream>
#include <algorithm>
#include <cstdio>
using namespace std;
typedef long long ll;
const int maxn=1e5+10;
int a[maxn],dp[maxn][5];//这里的0 1 2 3 4 5分别表示无操作 第一次买入 第一次卖出 第二次买入 第二次卖出
int main()
{
	int m;
	scanf("%d",&m);
	while (m--) {
		int n,i;
		scanf("%d",&n);
		for (i=0;i<n;i++) {
			scanf("%d",&a[i]);
		}
		dp[0][1]=-a[0];
		dp[0][3]=-a[0];
		for (i=1;i<n;i++) {
			dp[i][1]=max(dp[i-1][0]-a[i],dp[i-1][1]);
			dp[i][2]=max(dp[i-1][2],dp[i-1][1]+a[i]);
			dp[i][3]=max(dp[i-1][3],dp[i-1][2]-a[i]);
			dp[i][4]=max(dp[i-1][4],dp[i-1][3]+a[i]);
		}
		printf("%d\n",dp[n-1][4]);
	}
	return 0;
}

D - 数列分段

对于给定的一个长度为 NN 的正整数数列 Ai​,现要将其分成连续的若干段,并且每段和不超过 M(可以等于 M),问最少能将其分成多少段使得满足要求。

输入格式

第一行包含两个正整数 N,M,表示了数列 Ai​ 的长度与每段和的最大值;

第二行包含 N 个空格隔开的非负整数 Ai​。

输出格式

输出文件仅包含一个正整数,输出最少划分的段数。

样例

InputOutput
5 6
4 2 4 5 1
3

数据范围与提示

对于 20% 的数据,有N≤10;

对于 40% 的数据,有N≤1000;

对于 100% 的数据,有 N≤105,M≤109,M 大于所有数的最大值。

这个直接暴力即可

#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const int maxn=1e5+10;
int a[maxn];
int main()
{
	int m,n,i,cont=1;
	cin>>n>>m;
	ll sum=0;
	for (i=0;i<n;i++) {
		cin>>a[i];
		if (sum+a[i]>m) {
			sum=a[i];
			cont++;
		} else {
			sum+=a[i];
		}
	}
	cout<<cont<<endl;
	return 0;
}

 E - 最大子段和

N个整数组成的序列a[1],a[2],a[3],…,a[n],求该序列如a[i]+a[i+1]+…+a[j]的连续子段和的最大值。当所给的整数均为负数时和为0。

例如:-2,11,-4,13,-5,-2,和最大的子段为:11,-4,13。和为20。

Input

第1行:整数序列的长度N(2 <= N <= 50000) 第2 - N + 1行:N个整数(-10^9 <= A[i] <= 10^9)

Output

输出最大子段和。

Sample

InputOutput
6
-2
11
-4
13
-5
-2
20

 求最大子段和,一旦前面的和出现负数,就会影响后面的最大值,所以当出现负数时应舍去

#include <iostream>
using namespace std;
typedef long long ll;
const int maxn=5e4+10;
int a[maxn];
int main()
{
	int n,i;
	ll ans=0;
	cin>>n;
	for (i=0;i<n;i++) {
		cin>>a[i];
	}
	ll sum=0;
	for (i=0;i<n;i++) {
		sum+=a[i];
		ans=max(ans,sum);
		if (sum<0) {//舍去负数
			sum=0;
		}
	}
	cout<<ans<<endl;
	return 0;
}

F - 活动安排问题

有若干个活动,第i个开始时间和结束时间是[Si,fi),同一个教室安排的活动之间不能交叠,求要安排所有活动,最少需要几个教室? 

Input

第一行一个正整数n代表活动的个数。 之后n行每行两个数s,t,分别为开始/结束时间。 其中n<=10000,0<=t1 <1e9。

Output

一行包含一个整数表示最少教室的个数。

Sample

InputOutput
3
1 2
3 4
2 9
2
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const int maxn=1e4+10;
typedef struct date{
	int x;
	int y;
	int z;
}SJ;
ll b[maxn];
bool cmp(SJ a,SJ b) {
	if (a.x==b.x) {
		return a.z<b.z;
	} else {
		return a.x<b.x;
	}
}
int main()
{
	int n,i,sum=1,t;
	SJ a[maxn];
	cin>>n;
	for (i=0;i<n;i++) {
		cin>>a[i].x>>a[i].y;
		a[i].z=a[i].y-a[i].x;
	}
	sort(a,a+n,cmp);
	b[0]=a[0].y;
	for (i=1;i<n;i++) {
		for (t=0;t<sum;t++) {
			if (a[i].x>=b[t]) {
				b[t]=a[i].y;
				break;
			} 
		}
		if (t==sum) {//创建新房间
			b[sum]=a[i].y;
			sum++;
		}
	}
	cout<<sum<<endl;
	return 0;
}

 G - 线段

数轴上有 nn 条线段,选取其中 kk 条线段使得这 kk 条线段两两没有重合部分,问 kk 最大为多少。

输入格式

第一行为一个正整数 nn;

在接下来的 nn 行中,每行有 22 个数 a_i, b_iai​,bi​,描述每条线段。

输出格式

输出一个整数,为 kk 的最大值。

样例

InputOutput
3
0 2
2 4
1 3
2

数据范围与提示

对于 20% 的数据,n≤10;

对于 50% 的数据,n≤103;

对于 70% 的数据,n≤105;

对于 100% 的数据,n≤106, 0≤ai​<bi​≤106。

 与老板开会问题为一个类型的题目,先进行排序,然后就求取最大数量

#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const int maxn=1e6+10;
struct date {
	int a;
	int b;
	int c;
}a[maxn];
bool cmp (date e,date f){
	if (e.b==f.b) {
		return e.c<f.c;
	} else {
		return e.b<f.b;
	}
}
int main()
{
	int n,i;
	cin>>n;
	for (i=0;i<n;i++) {
		cin>>a[i].a>>a[i].b;
		a[i].c=a[i].b-a[i].a;
	}
	sort(a,a+n,cmp);
	int cont=1,end=a[0].b;
	for (i=1;i<n;i++) {
		if (a[i].a>=end) {
			cont++;
			end=a[i].b;
		}
	}
	cout<<cont<<endl;
	return 0;
}

 H - 定价

在市场上有很多商品的定价类似于 999 元、4999 元、8999 元这样。它们和 1000 元、5000 元和 9000 元并没有什么本质区别,但是在心理学上会让人感觉便宜很多,因此也是商家常用的价格策略。不过在你看来,这种价格十分荒谬。于是你如此计算一个价格 pp(pp 为正整数)的荒谬程度:

  1. 首先将 pp 看做一个由数字组成的字符串(不带前导 00);
  2. 然后,如果 pp 的最后一个字符是 00,就去掉它。重复这一过程,直到 pp 的最后一个字符不是 00;
  3. 记 pp 的长度为 aa,如果此时 pp 的最后一位是 55,则荒谬程度为 2a - 12a−1;否则为 2a2a。

例如,850 的荒谬程度为 3,而 880 则为 4,9999 的荒谬程度为 8。

现在,你要出售一样闲置物品,你能接受的定价在 [L, R][L,R] 范围内,你想要给出一个荒谬度最低的价格。

输入格式

输入文件的第一行包含一个正整数 T,表示测试数据的数目。

每个测试数据占单独的一行,包含两个空格分隔的正整数 L,R,表示定价的区间。

输出格式

对于每个测试数据,在单独的一行内输出结果。如果荒谬度最低的价格不唯一,输出最小的那个。

样例

InputOutput
3
998 1002
998 2002
4000 6000
1000
1000
5000

数据范围与提示

对于 100% 的数据,T≤100, 1≤L≤R≤109。

刚遇到这题时有点蒙,然后参考了别人的代码传送门

#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
int a[15]={1,10,100,1000,10000,100000,1000000,10000000,100000000,1000000000};
int jia (int t) {
	int shu=0;
	while (t%10==0) {
		t/=10;
		shu++;
	}
	return shu;
}
int las (int t) {
	while (t%10==0) t/=10;
	return t%10;
}
int tongji (int t) {
	int cont=0;
	while (t%10==0) t/=10;
	while (t>0) {
		t/=10;
		cont++;
	}
	return cont;
}
int main()
{
	int m;
	cin>>m;
	while (m--) {
		int l,r,i;
		int ans,sign=99999;
		cin>>l>>r;
		for (i=l;i<=r;i+=a[jia(i)]) {
			int last;
			if (las(i)==5) {
				last=1;
			} else {
				last=0;
			}
			if (sign>2*tongji(i)-last) {
				sign=2*tongji(i)-last;
				ans=i;
			}
		}
		cout<<ans<<endl;
	}
	return 0;
}

 I - 排队接水

n个人一起排队接水,第i个人需要b[i]的时间来接水。(1<=n<=1000,0<=b[i]<=1000)

同时只能有一个人接水,正在接水的人和没有接水的人都需要等待。

完成接水的人会立刻消失,不会继续等待。

你可以决定所有人接水的顺序,并希望最小化所有人等待时间的总和。

Input

第一行一个整数n 接下来n行,每行一个整数表示b[i]

Output

一行一个整数,表示所有人等待时间的总和的最小值

Sample

InputOutput
3
1
2
3
10

 思考一下,当一个人在接水时,后面的人同样在等,所以要让时间短的人先接,这样可以让后面的人等的时间短一些

#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const int maxn=1010;
int a[maxn];
bool cmp (int c,int b) {
	return c<b;//升序排列
}
int main()
{
	int n,i;
	cin>>n;
	for (i=0;i<n;i++) {
		cin>>a[i];
	}
	sort(a,a+n,cmp);
	ll sum=a[0],cont=0;
	for (i=1;i<n;i++) {
		cont+=a[i-1];
		sum+=cont+a[i];
	}
	cout<<sum<<endl;
	return 0;
}

 J - 拼成最小的数 V2

设有n个正整数,将它们连接成一排,组成一个最小的多位整数。

例如:

n=2时,2个整数32,321连接成的最小整数为:32132,

n=4时,4个整数55,31,312, 33 联接成的最小整数为:312313355

Input

第1行:1个数N。(2 <= N <= 10000) 第2 - N+1行:每行1个正整数。(1 <= A[i] <= 10^9)

Output

输出拼在一起的最小整数。

Sample

InputOutput
4
55
31
312
33
312313355
#include <iostream>
#include <algorithm>
#include <string>
using namespace std;
typedef long long ll;
const int maxn=1e4+10;
string a[maxn];
bool cmp (string a,string b) {
	return a+b<b+a;//string在这里可以直接进行拼接
}
int main()
{
	int n,i;
	cin>>n;
	for (i=0;i<n;i++) {
		cin>>a[i];
	}
	sort(a,a+n,cmp);
	for (i=0;i<n;i++) {
		cout<<a[i];
	}
	cout<<endl;
	return 0;
}

 L - 货币系统

在网友的国度中共有 n 种不同面额的货币,第 i 种货币的面额为 a[i],你可以假设每一种货币都有无穷多张。为了方便,我们把货币种数为 n、面额数组为 a[1\dots n]a[1…n] 的货币系统记作 (n,a)。

在一个完善的货币系统中,每一个非负整数的金额 x 都应该可以被表示出,即对每一个非负整数 x,都存在 n 个非负整数 t[i] 满足 a[i] \times t[i]a[i]×t[i] 的和为 x。然而,在网友的国度中,货币系统可能是不完善的,即可能存在金额 x 不能被该货币系统表示出。例如在货币系统 n=3, a=[2,5,9] 中,金额 1,3 就无法被表示出来。

两个货币系统 (n,a) 和 (m,b) 是等价的,当且仅当对于任意非负整数 x,它要么均可以被两个货币系统表出,要么不能被其中任何一个表出。

现在网友们打算简化一下货币系统。他们希望找到一个货币系统 (m,b),满足 (m,b) 与原来的货币系统 (n,a) 等价,且 m 尽可能的小。他们希望你来协助完成这个艰巨的任务:找到最小的 m。

Input

输入文件的第一行包含一个整数 T,表示数据的组数。 接下来按照如下格式分别给出 T 组数据。 每组数据的第一行包含一个正整数 n。接下来一行包含 n 个由空格隔开的正整数 a[i]。

Output

输出文件共有 T 行,对于每组数据,输出一行一个正整数,表示所有与 (n,a) 等价的货币系统 (m,b) 中,最小的 m。

Sample

InputOutput
2
4
3 19 10 6
5
11 29 13 19 17
2
5

这题昨天看完别人的题解后才明白可以转化成一个完全背包问题,用小的价钱来表示出大的价钱

#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
typedef long long ll;
const int maxn=1e5+10;
bool dp[maxn];
int a[110];
bool cmp (int a,int b) {
	return a<b;
}
int main()
{
	int m;
	cin>>m;
	while (m--) {
		int n,i;
		cin>>n;
		int mx=0;
		memset(dp,false,sizeof(dp));
		for (i=0;i<n;i++) {
			cin>>a[i];
			mx=max(mx,a[i]);
		}
		int ans=0;
		sort(a,a+n,cmp);
		dp[0]=true;
		for (i=0;i<n;i++) {
			if (dp[a[i]]==0) {//这里是看当前的价格是否已经被表示出来了
				ans++;
			}
			for (int t=0;t<=mx;t++) {
				if (dp[t]) {//如果该价格表示出来以后就加上的当前的货币来表示下一个价格
					dp[t+a[i]]=1;
				}
			}
		}
		cout<<ans<<endl;
	}
	return 0;
}

N - 接水问题二

n 个人一起排队接水,第 ii 个人的重要性是 a[i]a[i],需要 b[i]b[i] 的时间来接水。(1 <= n <= 100000,0 <= b[i] <= 1000,0 <= a[i] <= 1000)

同时只能有一个人接水,正在接水的人和没有接水的人都需要等待。

完成接水的人会立刻消失,不会继续等待。

你可以决定所有人接水的顺序,并希望最小化所有人等待时间乘以自己的重要性 a[i]a[i] 的总和,输出等待时间乘以自己的重要性的总和。

Input

第一行一个整数n。 以下n行,每行两个整数a[i]和b[i]。

Output

一行一个整数表示答案。

Sample

InputOutput
4
1 4
2 3
3 2
4 1
35

这个问题多了一个变量,那就不能只单单考虑时间的问题

a[1] a[2]  b[1] b[2]

先让a接水的时间:a[1]*a[2]+b[1]*(a[2]+b[2])

先让b接水的时间:b[1]*b[2]+a[1]*(a[2]+b[2])

相减就会发现就剩下b[1]a[2]和a[1]*b[2],那我们就根据这两项排序即可

#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const int maxn=1e5+10;
struct date {
	int a;
	int b;
}a[maxn];
bool cmp(date c,date d) {
	return d.a*c.b<c.a*d.b;
}
int main()
{
	int n,i;
	cin>>n;
	for (i=0;i<n;i++) {
		cin>>a[i].a>>a[i].b;
		if (a[i].a==0||a[i].b==0) {
			i--;
			n--;
		}
	}
	sort(a,a+n,cmp);
	ll ans=a[0].b*a[0].a,sum=0;
	for (i=1;i<n;i++) {
		sum+=a[i-1].b;
		ans+=(sum+a[i].b)*a[i].a;
	}
	cout<<ans<<endl;
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值