沈师PTA算法4贪心作业

这篇博客探讨了如何通过贪心算法解决月饼销售的最大收益问题和会场安排问题。在月饼销售问题中,首先计算每种月饼的单价,然后按单价从高到低排序,依次销售直到市场需求量满足。在会场安排问题中,按照活动的开始和结束时间进行排序,动态维护当前所需会场数,从而找出最小会场数。这两个问题都展示了贪心策略的有效性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

7-1 月饼

月饼是中国人在中秋佳节时吃的一种传统食品,不同地区有许多不同风味的月饼。现给定所有种类月饼的库存量、总售价、以及市场的最大需求量,请你计算可以获得的最大收益是多少。

注意:销售时允许取出一部分库存。样例给出的情形是这样的:假如我们有 3 种月饼,其库存量分别为 18、15、10 万吨,总售价分别为 75、72、45 亿元。如果市场的最大需求量只有 20 万吨,那么我们最大收益策略应该是卖出全部 15 万吨第 2 种月饼、以及 5 万吨第 3 种月饼,获得 72 + 45/2 = 94.5(亿元)。

输入格式:

每个输入包含一个测试用例。每个测试用例先给出一个不超过 1000 的正整数 N 表示月饼的种类数、以及不超过 500(以万吨为单位)的正整数 D 表示市场最大需求量。随后一行给出 N 个正数表示每种月饼的库存量(以万吨为单位);最后一行给出 N 个正数表示每种月饼的总售价(以亿元为单位)。数字间以空格分隔。

输出格式:

对每组测试用例,在一行中输出最大收益,以亿元为单位并精确到小数点后 2 位。

输入样例:

3 20
18 15 10
75 72 45

输出样例:

94.50
答案:
法1:
#include <iostream>
#include <string>
#include<algorithm>
#include<bits/stdc++.h>
#include<stack>
#include<set>
using namespace std;
typedef struct chan{
	float l;
	float mon;
    float dan;
}chan;
chan a[1001];
bool cmp(chan a, chan b){//单价排序,贵的在前面
	return a.dan>b.dan;
}
int main(){
	int n,D;//由题意定义正整数n,最大市场需求量D
	cin>>n>>D;
	int i;
	for(i=0;i<n;i++){  //输入n个每种月饼的库存量
		cin>>a[i].l;
	}
	for(i=0;i<n;i++){  //输入n个每种月饼的总售价
		cin>>a[i].mon;
		a[i].dan=float(a[i].mon*1.0/a[i].l);//求单价
	}
	sort(a,a+n,cmp);
	float sum=0;
	i=0;
	while(D>0){
		if(a[i].l>=D){
			sum+=D*a[i].dan;
			D=0;
		}
		else{
			sum+=a[i].l*a[i].dan;
			D=D-a[i].l;
		}
		
		i++;
		if(i==n){//当月饼全卖完的时候,主动退出循环。
			break;
		}
	}
	printf("%.2f",sum);
}

**思路:**先算出每种月饼的单价,按照单价从高到低排序,因为要求的是卖出月饼的最高价格,所以肯定先卖贵的,最贵的卖完卖第二贵的,然后依次循环。
最后的while循环是此题的难点,当此种月饼的库存大于需要的D时,只需要将D吨月饼全卖出去,再将D置零结束循环即可。
当当前月饼的库存小于所需要的D时,先把当前贵的月饼库存卖完,D变为D-当前库存,再用算的D和下一个月饼进行对比,知道D不为正整数即可;
此题用结构体定义会清晰很多,一个结构体就代表一种月饼,有单价,库存和总售价,如果定义数组的话需要单独的三个数组,非常不好理解和麻烦,结构体在这种情况下是最清晰的。
坑点:1、会存在月饼全卖完的情况,当D大于所有月饼之和的时候,要退出循环,也就是程序中(i==n)的时候,需要主动退出循环,否则会有段错误;
2、月饼的库存和售价都会有浮点数的可能,不要看样例给的都是int型就都设为int,需要认真读题,题目中只说了是正数,读题很重要。

法2:
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
struct mooncake{
    float num, price, unit;
};
int cmp(mooncake a, mooncake b) {
    return a.unit > b.unit;
}
int main() {
    int n, need;
    cin >> n >> need;
    vector<mooncake> a(n);
    for (int i = 0; i < n; i++) scanf("%f", &a[i].num);
    for (int i = 0; i < n; i++) scanf("%f", &a[i].price);
    for (int i = 0; i < n; i++) a[i].unit = a[i].price / a[i].num;
    sort(a.begin(), a.end(), cmp);
    float result = 0.0;
    for (int i = 0; i < n; i++) {
        if (a[i].num <= need) {
            result = result + a[i].price;
        } else {
            result = result + a[i].unit * need;
            break;
        }
        need = need - a[i].num;
    }
    printf("%.2f",result);
    return 0;
}

我个人对vector这个容器不是很理解,但是感觉跟Java中的HashMap有点相似。

附:一些vector常用函数实例解析

(1)push_back()

push_back(x) 就是在 vector 后面添加一个元素 x ,时间复杂度为 O(1)。

(2)pop_back()

pop_back(x) 用以删除 vector 的尾元素,时间复杂度为 O(1)。

(3)size()

size() 用来获得 vector 中元素的个数,时间复杂度为 O(1)。size()返回的是 unsigned 类型,不过一般来说用 %d 不会出很大问题,这一点对所有 STL 容器都是一样的。

(4)clear()

clear() 用来清空 vector 中的所有元素,时间复杂度为 O(N),其中 N 为 vector 中元素的个数。

(5)insert()

insert(it,x) 用来向 vector 的任意迭代器 it 处插入一个元素 x,时间复杂度为 O(N)。

(6)erase()

  1. 删除单个元素。

erase(it) 即删除迭代器为 it 处的元素。

  1. 删除一个区间内的所有元素。

erase(first,last) 即删除 [first,last) 内的所有元素。

7-2 会场安排问题

题目来源:王晓东《算法设计与分析》

假设要在足够多的会场里安排一批活动,并希望使用尽可能少的会场。设计一个有效的
贪心算法进行安排。(这个问题实际上是著名的图着色问题。若将每一个活动作为图的一个
顶点,不相容活动间用边相连。使相邻顶点着有不同颜色的最小着色数,相应于要找的最小
会场数。)

输入格式:

第一行有 1 个正整数k,表示有 k个待安排的活动。
接下来的 k行中,每行有 2个正整数,分别表示 k个待安排的活动开始时间和结束时间。时间
以 0 点开始的分钟计。

输出格式:

输出最少会场数。

输入样例:

5
1 23
12 28
25 35
27 80
36 50 

输出样例:

在这里给出相应的输出。例如:

3
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
struct activity
{
    int time;
    bool status; //标注是活动开始时间还是结束时间,true 开始,false 结束
    activity(int time, bool status) : time(time), status(status) {}
    bool operator<(activity other)
    {
        return time < other.time;
    }
};
vector<activity> v;
int solve()
{
    int n = v.size();
    int ans = 0;
    int num = 0;
    for (int i = 0; i < n; i++)
    {
        if (v[i].status)
        {
            num++; //开始,会场数增加一
        }
        else
        {
            num--; //结束,会场数减少一
        }

        //更新需要的会场数
        if (num > ans && (i == n - 1 || v[i].time < v[i + 1].time))
        {
            ans = num;
        }
    }
    return ans;
}
int main()
{
    int n;
    cin >> n;
    int beginTime;
    int endTime;
    while (n--)
    {
        cin >> beginTime;
        v.push_back(activity(beginTime, true));
        cin >> endTime;
        v.push_back(activity(endTime, false));
    }
    sort(v.begin(), v.end()); //按时间升序排序
    cout << solve() << endl;
}

解题思路:

用变量num来记录需要使用的会场数

用变量ans来记录已开辟的会场数

用vector来保存时间点

对于会场的使用,就两种情况,活动开始时使用会场(num++),活动结束时回收会场(num–)

我们要找到这批活动中使用的最少会场数,则可以根据活动的先后使用时间来进行升序排序,这样就能在一系列活动的使用会场(num++)和回收会场(num–)中找到总共最少要开辟多少会场才能满足这些活动所需了

对于需要的会场数大于已开辟的会场数num>ans,则ans=num,更新开辟的会场

当满足num>ans时,如果当前时间点和下一个时间点相同时,有两种情况,下一个时间点要么是开始使用,要么是结束使用,如果是结束使用的话,则开始时间和结束时间相同,不需要使用会场,就不更新了,如果是开始时间的话,在下一轮更新开辟的会场数也是一样的(就相当于在下一轮同时开辟两个会场)

当前时间点是倒数第二个的话,最后一个时间点一定是结束时间,这时一定要更新ans(不用再判断当前时间点和下一个时间点的关系)

7-3 装箱问题

假设有N项物品,大小分别为s1s_1s1s2s_2s2、…、sis_isisNs_NsN,其中sis_isi为满足1≤sis_isi≤100的整数。要把这些物品装入到容量为100的一批箱子(序号1-N)中。装箱方法是:对每项物品, 顺序扫描箱子,把该物品放入足以能够容下它的第一个箱子中。请写一个程序模拟这种装箱过程,并输出每个物品所在的箱子序号,以及放置全部物品所需的箱子数目。

输入格式:

输入第一行给出物品个数N(≤1000);第二行给出N个正整数sis_isi(1≤sis_isi≤100,表示第i项物品的大小)。

输出格式:

按照输入顺序输出每个物品的大小及其所在的箱子序号,每个物品占1行,最后一行输出所需的箱子数目。

输入样例:

8
60 70 80 90 30 40 10 20

输出样例:

60 1
70 2
80 3
90 4
30 1
40 5
10 1
20 2
5
解题思路:

思路图

#include <iostream>
#include <cstring>
using namespace std;

char box[1010];

int main()
{
	memset(box,100,sizeof(box));
	int N;
	int t;
	int num=0;
	cin>>N;
	int temp = N;
	while(temp--)
	{
		cin >> t;
		for (int i=0;i<N;i++)
		{
			int a = box[i];
			if (a >= t)
			{
				if (a==100)
				    num++;

				box[i] -= t;
				cout << t << " " << i+1 <<endl;
				break;
			}
		}
	}
	cout << num << endl;

	return 0;
}

关于memset的用法,需要知道,它是以一个字节为单位批量处理,由于本题数据范围小(1000以内),故刚好可以用char 数组来批量预处理,但是,若改成int 数组再进行menset则会出错,因为int为4个字节,这样每个数组里面的数就不是100而是(100<<24)+(100<<16)+(100<<8)+100 相当于每个字节里都是100。

7-4 森森快递

森森开了一家快递公司,叫森森快递。因为公司刚刚开张,所以业务路线很简单,可以认为是一条直线上的N个城市,这些城市从左到右依次从0到(N−1)编号。由于道路限制,第i号城市(i=0,⋯,N−2)与第(i+1)号城市中间往返的运输货物重量在同一时刻不能超过CiC_iCi公斤。

公司开张后很快接到了Q张订单,其中j张订单描述了某些指定的货物要从SjS_jSj号城市运输到TjT_jTj号城市。这里我们简单地假设所有货物都有无限货源,森森会不定时地挑选其中一部分货物进行运输。安全起见,这些货物不会在中途卸货。

为了让公司整体效益更佳,森森想知道如何安排订单的运输,能使得运输的货物重量最大且符合道路的限制?要注意的是,发货时间有可能是任何时刻,所以我们安排订单的时候,必须保证共用同一条道路的所有货车的总重量不超载。例如我们安排1号城市到4号城市以及2号城市到4号城市两张订单的运输,则这两张订单的运输同时受2-3以及3-4两条道路的限制,因为两张订单的货物可能会同时在这些道路上运输。

输入格式:

输入在第一行给出两个正整数NQ(2≤N10510^5105, 1≤Q10510^5105),表示总共的城市数以及订单数量。

第二行给出(N−1)个数,顺次表示相邻两城市间的道路允许的最大运货重量CiC_iCii=0,⋯,N−2)。题目保证每个CiC_iCi是不超过2312^{31}231的非负整数。

接下来Q行,每行给出一张订单的起始及终止运输城市编号。题目保证所有编号合法,并且不存在起点和终点重合的情况。

输出格式:

在一行中输出可运输货物的最大重量。

输入样例:

10 6
0 7 8 5 2 3 1 9 10
0 9
1 8
2 7
6 3
4 5
4 2

输出样例:

7

样例提示:我们选择执行最后两张订单,即把5公斤货从城市4运到城市2,并且把2公斤货从城市4运到城市5,就可以得到最大运输量7公斤。

#include<bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
#define ll long long
const int N=2e5+5;
ll lazy[N<<2],Min[N<<2];
int n,m,a[N];
void PushDown(int rt)
{
    if(!lazy[rt])return;
    lazy[rt<<1]+=lazy[rt],lazy[rt<<1|1]+=lazy[rt];
    Min[rt<<1]-=lazy[rt],Min[rt<<1|1]-=lazy[rt];
    lazy[rt]=0;
}
void Build(int l,int r,int rt)
{
    if(l==r)
    {
        Min[rt]=a[l];
        return;
    }
    int mid=(l+r)>>1;
    Build(lson),Build(rson);
    Min[rt]=min(Min[rt<<1],Min[rt<<1|1]);
}
ll QMin(int L,int R,int l,int r,int rt)
{
    if(L<=l&&r<=R)return Min[rt];
    PushDown(rt);
    int mid=(l+r)>>1;
    ll ans=1e18;
    if(L<=mid)ans=min(ans,QMin(L,R,lson));
    if(R>mid)ans=min(ans,QMin(L,R,rson));
    return ans;
}
void Update(int L,int R,int c,int l,int r,int rt)
{
    if(L<=l&&r<=R)
    {
        lazy[rt]+=c,Min[rt]-=c;
        return;
    }
    PushDown(rt);
    int mid=(l+r)>>1;
    if(L<=mid)Update(L,R,c,lson);
    if(R>mid)Update(L,R,c,rson);
    Min[rt]=min(Min[rt<<1],Min[rt<<1|1]);
}
pair<int,int>P[N];
int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=1; i<n; i++)scanf("%d",&a[i]);
    Build(1,n-1,1);
    for(int i=0,l,r; i<m; i++)
    {
        scanf("%d%d",&l,&r);
        if(l>r)swap(l,r);
        P[i]= {r,l};
    }
    sort(P,P+m);
    ll ans=0;
    for(int i=0; i<m; i++)
    {
        ll num=QMin(P[i].se+1,P[i].fi,1,n-1,1);
        ans+=num;
        Update(P[i].se+1,P[i].fi,num,1,n-1,1);
    }
    cout<<ans;
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

飞b6

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值