Day 3 贪心 排序

贪心算法(见c基础)

排序(见c基础)

排兵布阵 快排函数的应用

Description
总所周知,韩信是一位神勇的军事家。某日夜幕,敌方突然来袭,韩信作为塞外将帅吹响紧急的号角。各个帐内的士兵听见号角立即集合,站成一排,排成连续的一队。但是士兵太多了,如果让他们集合耗费太多精力就没有办法打好接下来的胜仗,因此韩信希望选择一个最优的方案使得所有士兵从帐内移动到将要站队的位置的曼哈顿距离和最小!

Input
第一行输入一个整数 n 空格 左括号 1 小於等於 n 小於等於 10 的 5 次方 右括号 表示士兵数量;

接下来 n 行,每行输入两个整数 x 下標 i 逗號 空格 y 下標 i 空格 左括号 負 10 的 9 次方 小於等於 x 下標 i 逗號 空格 y 下標 i 小於等於 10 的 9 次方 右括号 表示第 i 名士兵帐篷的坐标。

Output
请输出一个整数,表示所有士兵从帐内移动到将要站队的位置的距离和。

Hint
站成一排的意思是最终队列中士兵们的纵坐标相同,横坐标排成连续的一段区间。

【解题思路】
本题提到了一个“曼哈顿距离”的概念,其实也就是所谓的“L1距离”。这种距离只是将各个维度上的距离简单相加,因此最终的距离之和可以看成是将各个维度上的距离之和相加,换言之,每个维度的问题可以独立解决。

注意到,x维度和y维度上的问题很类似,但有所不同:y维度上要求所有点最终汇聚至一点,x维度上要求所有点最终连续排列。

首先分析y维度,我们假设站队位置坐标为y,该位置下方有k名士兵,上方有n-k名士兵,可列出下式

我们考虑,如果这时将站队点往下移动一格,那么距离会变化n-2k;如果往上移动一格,那么距离会变化2k-n。换言之,如果k>n/2,则下移可以优化距离,反之如果k<n/2,则上移可以优化距离。也就是说,我们只需要让k==n/2即可使距离达到最小值,也就是站队点取各个士兵y坐标的中位数即可。

再分析x维度,假设排头位置为x,首先列出下式:

将式子化到这步,我们就自然将问题转化成与y维度一样的问题了,只不过y换成了x,y_i换成了x_i-i+1(x_i提前处理好,从小到大排列),按照y维度的流程走一遍即可

【代码细节】
首先对各个士兵的x和y坐标分别从小到大排序(这是基于各个维度的独立性),并令x_i -= i-1,于是站队点就为(x[(n+1)/2],y[(n+1)/2]),即各个维度的中位数,算出距离即可。

【坑点】
如题,别忘了开long long。

//https://blog.csdn.net/What_If_I/article/details/118500101
#include<stdio.h>
#include<stdlib.h>
#include<math.h>
long long int x[1000000],y[1000000];
long long int i=0,sumx=0,sumy=0,midx,midy,n,t;


int cmp(const void*pa, const void*pb)  {

     return *(int *)pa-*(int *)pb; 
}


int main(){
	memset(x,0,sizeof(x));
	scanf("%lld\n",&n);
	t=n;
	while(n--){
		scanf("%lld %lld\n",&x[n],&y[n]);
	}
	qsort(x,t,sizeof(long long int),cmp);
	qsort(y,t,sizeof(long long int),cmp);
	//计算 y维度 
	midy=y[t/2];
	for(i=0;i<t;i++){
		sumy+=fabs(y[i]-midy);
	} 
	//计算x维度 
	for(i=0;i<t;i++){
		x[i]-=i;
	}
	qsort(x,t,sizeof(long long int),cmp);
	midx=x[t/2];
	for(i=0;i<t;i++){
		sumx+=fabs(x[i]-midx);
	} 
	sumx+=sumy;
	printf("%lld\n",sumx);
	return 0;
}

结构体的qsort

Description
小张经常为了事情太多安排不开而苦恼。现在他手头有 n 项任务,每项任务都有一个开始时间 s_i 和结束时间 e_i 。要想完成一个任务必须从开始时间做到结束时间,并且同一时间小张只能进行一项任务。
小张想知道他最多可以完成几项任务。
Input
第一行一个整数 n(1 \leq n \leq 300000 ) ,表示小张手头任务的个数。

接下来 n 行,每行两个整数 s_i,e_i(1 \leq s_i < e_i \leq 10^9 ) ,表示任务的开始时间和结束时间。

Output
一行一个整数,表示小张最多可以完成几项任务。
【解题思路】
本题题意很简单,从n项任务中选出时间不冲突的任务,使得选出的任务数量尽量多。实质就是,如果现在有几个时间冲突的任务,我选出哪个是最有利于后续选择的。我们不妨找几个例子来对比一下:

A=[5,13], B=[6,12],很明显选B,因为B开始比A晚,结束比A早,B引起冲突的A也一定会引起冲突,反之则不一定。

再看A=[5,10], B=[7,13],如果不确定,看看样例,你会发现选A,为什么?虽然本例与上例一样,B开始比A晚,但在前面任务都已安排好且这两个任务都能选的情况下,A开始早这件事对选不选A已经没有影响了,重点看任务什么时候结束,因为这才是影响后续选择的关键,所以一定要选结束时间早的,才能尽量少与后续任务产生冲突。

于是,我们的贪心方案诞生了:总是选择当前能选的结束时间最早的任务。

【代码细节】
将任务的开始时间和结束时间存储在一个结构体中,并将该结构体按结束时间从小到大排序,然后遍历一遍,能选则选即可。

【坑点】
似乎…无?
代码:

//结构体的qsort  理解其用法https://blog.csdn.net/yisandezhuiqiu/article/details/52101667        有至少一处错误 
#include<stdio.h>
#include<stdlib.h>
long long int n,t,ans,current;
struct time{
	int s;
	int e;
}in[1000000];
int cmp(const void *a,const void *b){
	struct time *c=(time *)a;
	struct time *d=(time *)b;
	return c->e-d->e;//记住写法 
} 
int main(){
	scanf("%lld\n",&n);
	t=n;
	while(n--){
		scanf("%d %d",&in[n].s,&in[n].e);
		getchar();
	}
	qsort(in,t,sizeof(in[0]),cmp);
	current=in[n].e;
	ans=1;
	for(int i=1;i<t;i++){
		if(in[i].s>=current){
			ans++;
			current=in[i].e;
		}
	}
	printf("%lld\n",ans);
	return 0;
}

合并果子(太典了)

需要掌握的:小根堆的使用(c++有现成的,不过刚开始学习最好会用c语言去实现)
在一个果园里,多多已经将所有的果子打了下来,而且按果子的不同种类分成了不同的堆。多多决定把所有的果子合成一堆。

每一次合并,多多可以把两堆果子合并到一起,消耗的体力等于两堆果子的重量之和。可以看出,所有的果子经过n-1次合并之后,就只剩下一堆了。多多在合并果子时总共消耗的体力等于每次合并所耗体力之和。

因为还要花大力气把这些果子搬回家,所以多多在合并果子时要尽可能地节省体力。假定每个果子重量都为1,并且已知果子的种类数和每种果子的数目,你的任务是设计出合并的次序方案,使多多耗费的体力最少,并输出这个最小的体力耗费值。

例如有3种果子,数目依次为1,2,9。可以先将1、2堆合并,新堆数目为3,耗费体力为3。接着,将新堆与原先的第三堆合并,又得到新的堆,数目为12,耗费体力为12。所以多多总共耗费体力=3+12=15。可以证明15为最小的体力耗费值。

输入
输入包括两行,第一行是一个整数n(1<=n<=10000),表示果子的种类数。第二行包含n个整数,用空格分隔,第i个整数ai(1<=ai<=10000)是第i种果子的数目。

输出
输出包括一行,这一行只包含一个整数,也就是最小的体力耗费值。输入数据保证这个值小于231。
代码:

小根堆  
//以下解法适合数据较小时使用  
#include<stdio.h>
#include<stdlib.h>
int n,i,j;
long long int heavy=0,temp=0;
long long int a[10001]={0};

int cmp(const void *a,const void *b){
	return *(int *)a-*(int *)b;
}
void get(long long int a[])
{
	for(i=0;i<n-1;i++){
		if(a[i]<=a[i+1]){
			heavy+=a[i]+a[i+1];
			a[i+1]+=a[i];
		}
		else{
			for(j=i;j<n-1;j++){
				if(a[j]>a[j+1]){
					temp=a[j];
					a[j]=a[j+1];
					a[j+1]=temp;
				}	//这里使用二分查找会更快
				else
					break;
			}
			heavy+=a[i]+a[i+1];
			a[i+1]+=a[i];
		}	
	}
	printf("%lld\n",heavy);
}
int main(){
	scanf("%d\n",&n);
	if(n==0||n==1){
	
		printf("0\n");
		return 0;
	}
	
	
	for(i=0;i<n;i++){
		scanf("%lld",&a[i]);
		getchar();
	}
	qsort(a,n,sizeof(a[0]),cmp);
	get(a);
	return 0;
}
c++ 优先队列 

#include<cstdio>
#include <iostream>
#include <algorithm>
#include <queue>

using namespace std;
int ans1,ans2,temp;
long long int ans;
priority_queue<int ,vector<int>,greater<int> > q;
 int n,i;
int main()
{
    scanf("%d",&n);
    while(n--)
    {
        scanf("%d",&i);
        q.push(i);
    }
    while(q.size() != 1)
    {
        ans1 = q.top();
        q.pop();
        ans2 = q.top();
        q.pop();
        temp = ans1+ans2;
        q.push(temp);
        ans += temp;
    } 
    printf("%lld",ans);
    printf("\n");
    return 0;
}

水晶球

Description
和许多同龄女孩子一样,久莲也喜欢水晶球。

还有 10 天,就是心心念念的他生日了。

久莲希望把全世界最大最好看的水晶球送给他。

她找到了宝石收藏家亚瑟斯,希望能够寻求他的帮助。

亚瑟斯很快被打动了,拿出了精心收集的 n 块美丽的水晶石,这些水晶石初始是长宽高分别为 a 逗號 空格 b 逗號 空格 c 的长方体。亚瑟斯许诺久莲可以从中取走 1 块水晶石作为她礼物的原材料。

同时亚瑟斯有一种魔法,如果这两块长方形水晶石在某一个面能够完美的契合在一起(完美的契合是指这两个长方形面全等),那么可以将它们融合成一块完整的大石头,如果真的实现的话,那么久莲就可能打磨出更大的水晶球啦!

久莲太希望把最美最大的水晶球送给他了,你快帮帮她如何选择吧。

Input
第一行输入一个正整数 n 空格 左括号 1 小於等於 n 小於等於 10 的 5 次方 右括号;

接下来 n 行中,第 i 行输入三个正整数 a 下標 i 逗號 空格 b 下標 i 逗號 空格 c 下標 i 空格 左括号 1 小於等於 a 下標 i 逗號 空格 b 下標 i 逗號 空格 c 下標 i 小於等於 10 的 9 次方 右括号 表示第 i 块水晶石的长宽高。注意可能有两个长得一模一样的水晶石,但是在这种情况下还是将它们视作是两块不同的水晶石。

Output
第一行请输出一个正整数 k 空格 左括号 1 小於等於 k 小於等於 2 右括号,表示久莲选择的水晶球数量。

第二行请输出 k 个正整数,如果 k 等於 1,请输出一个正整数 x 空格 左括号 1 小於等於 x 小於等於 n 右括号 表示久莲选择的水晶石。如果 k 等於 2,则请输出两个正整数 x 逗號 空格 y 空格 左括号 1 小於等於 x 逗號 空格 y 小於等於 n 右括号 (用空格间隔),表示久莲希望亚瑟斯帮她将编号为 x 和 y 的水晶石融合成一块更大的水晶石,并选择用这块水晶石来打磨加工。请注意,这两块水晶石必须满足 “完美契合” 的条件,否则这个选择不合法。如果有多种最优的选择,则你可以输出任意一种合法的最优方案。

Hint
对于样例,如果久莲选择第六个水晶球,那么她可以打磨成半径为 r 等於 2.5 的水晶球,这是最优的选择。

【14-水晶球】
【解题思路】
本题题干有些长,大概简述一下题意:有n个长方体,每个长方体都可以被打磨成一个水晶球,水晶球的直径等于对应长方体的最短棱长。此外,还可以将两个长方体合并(但只允许合并一次),合并的条件是两个长方体有一个面重合,通过这个面将两个长方体接起来。

很显然,k=1的情况就是选出最短棱长最长的长方体。而对于k=2的情况,我们首先考虑,怎样合并能使打磨出的水晶球变大?很显然,只有让两个长方体的最短边相接,才能使答案有可能变大,换言之,重合面必须是两条较长边构成的面。因此,我们只需将长方体按长边、中边、短边依次排序,这样只需检查每两个相邻的长方体的合并情况即可。

【代码细节】
长方体数据用结构体储存,结构体中信息包括编号和三边长度。

输入每个长方体的长宽高后,对长宽高做排序,称之为短边、中边、长边。

首先找出短边的最大值,作为一个备用答案。再将长方体按长边、中边、短边依次排序,如果两个相邻的长方体能合并,就确定出大长方体的最短边(可能是两个短边和,也可能是中边),与当前答案相比,如果更优就做好更新,注意还要把k更新为2。

【坑点】
注意长方体合并后,最短边不一定变成中边,也不一定是两短边之和,注意比较。

更新时注意将k一起更新,存储答案时不仅要储存当前最优解,还要储存最优解对应的编号。

代码:

//https://blog.csdn.net/weixin_43787043/article/details/108594129?spm=1001.2014.3001.5501  sort函数使用学习   与qsort对比 
#include<stdio.h>
#include<stdlib.h>
struct material{
	long long int id;
	long long int length;
	long long int  width;
	long long int height;
}ball[110000];
int cmp1 ( const void *a , const void *b ) { 
return *(long long int *)a - *(long long int *)b; 
} 

int cmp2 (const void *e,const void *f){
	struct material *c=(material *)e;
	struct material *d=(material *)f;
	if(c->length!=d->length){
		return c->length>d->length?1:-1;
	}
	else if(c->width!=d->width){
		return c->width>d->width?1:-1;
	}
	else
		return c->height>d->height?1:-1;
}//三级结构体 

long long int n,i,temp[3],hmax=0,htemp=0,x_id=0,y_id=0;

int main(){
	scanf("%lld\n",&n);
	for(i=0;i<n;i++){
		scanf("%lld %lld %lld\n",&temp[0],&temp[1],&temp[2]);
		qsort(temp,3,sizeof(temp[0]),cmp1);
		ball[i].id=i+1;
		ball[i].length=temp[2];
		ball[i].width=temp[1];
		ball[i].height=temp[0];
		if(ball[i].height>hmax){
			hmax=ball[i].height;
			x_id=ball[i].id;
		}
	}
	qsort(ball,n,sizeof(ball[0]),cmp2);
	for(i=0;i<n-1;i++)//i为n-1 不然会越界 
		if(ball[i].length==ball[i+1].length&&ball[i].width==ball[i+1].width){
			htemp=ball[i].height+ball[i+1].height;
			htemp=htemp>ball[i].width?ball[i].width:htemp;
			if(htemp>hmax){
				hmax=htemp;
				x_id=ball[i].id;
				y_id=ball[i+1].id;
			}		
		}
	if(y_id==0)
		printf("1\n%lld\n",x_id);
	else
		printf("2\n%lld %lld\n",x_id,y_id);
	return 0;		
}



北理工的恶龙

Description
小张最近沉迷上一款手机游戏北理工的恶龙。在这个游戏中你通过提升攻击力击败恶龙,打败所有恶龙后你可以获得游戏的胜利。
在这款游戏中,每一条恶龙有一个难度值 x_i 和一个经验值 y_i 。游戏中的英雄具有攻击力 A 。游戏一开始英雄的攻击力 A=0 。打到一条恶龙你的攻击力需要大于等于难度值 x_i 。在你击败恶龙以后,你的攻击力会增加经验值 y_i 。
当然,你有时需要额外提升你的攻击力,此时你只需氪金1元,就能增长一点攻击力。小张想知道,如果他自己决定挑战恶龙的顺序,要想击败所有恶龙至少需要氪金多少钱?
Input
第一行一个数 n(1 \leq n \leq 200000 ) 。
接下来 n 行每行两个数 x_i,y_i(0 \leq x_i \leq 1000000, -1000000 \leq y_i \leq 1000000 ) 。
Output
一个整数,表示小张最少需要氪金多少钱。
Notes
直接打败第一条恶龙,此时 A=1 ,花费0元。
直接打败第二条恶龙,此时 A=2 ,花费0元。
氪金3元,此时 A=5 ,打败第三条恶龙,此时 A=3 。
最后直接打败第四条恶龙。
【解题思路】
个人感觉是今天最有意思也最考验水平的贪心题。本题的题意很简单,确定一个杀龙的顺序,使得氪金最少,氪金的作用在于弥补不足的攻击力。

首先我们注意到,有的龙打掉以后获得经验是正的,而有的是负的,打这样的龙是出力不讨好的,我们没有必要把它们放在前面打。因此,杀龙的第一个原则就是先杀正收益的龙,再杀负收益的龙。

对于正收益部分,按照打游戏的常识,我们肯定先打小怪刷刷经验,再打boss(如果要严谨证明的话,我们可以考虑如果先打boss,我们可能需要先氪金到boss的难度,但这一部分氪金我本可以通过打一些小怪刷经验来省掉)。因此,我们只需按难度值排序,能杀则杀,不能杀则氪金。

对于负收益部分,则是本题较为核心的部分,到底该按什么排序?如果还是按照难度排序,我们考虑一种极端情况,你现在攻击力是1e9,一条龙难度是1e9,经验值是-1e9,另一条龙难度是10,经验值是-1,你会发现,这时应该先杀难度小的才对,正收益部分的贪心逻辑不适用了;那按经验值排序?先杀亏得少的?考虑这样一种情况,你现在攻击力是9,一条龙难度是9,经验值是-2,另一条龙难度是6,经验值是-1,你会发现,这时先杀亏得多的反而不用氪金。两条路都不对,这时候应该怎么考虑?

其实对于这种排序的贪心问题,我们确定策略往往可以采用交换的方法。如果交换后结果更优,那么就交换。我们考虑一条龙难度是x,杀掉会亏损y(为方便叙述,这里直接称亏损,y取绝对值),那么你其实可以理解为,一条龙难度是x,杀掉不会对你造成亏损,但是却让其他龙难度都增加了y,这样就使问题转化成了类似正收益的部分。如果这样理解,我们考虑两条龙,一条难度x_1,杀掉后亏损y_1,另一条难度x_2,杀掉后亏损y_2。首先考虑到无论先杀1还是先杀2,对除这两条龙以外的其他龙难度都是增加y_1+y_2,因此我们通过交换得到的局部最优解不会对其他局部造成影响。所以,我们可以通过维护这种局部最优解来得到全局最优解。如果你先杀1后杀2,那么相当于难度为x_2+y_1,如果先杀2后杀1,那么相当于难度为x_1+y_2。很显然,由于我现在打怪是零收益,所以我只需要让后面的怪打起来难度更低就好,所以如果x_2+y_1<x_1+y_2,那我就先杀1,否则就先杀2。这样的比较看起来有些奇怪,好像比较对象的两个参数纠缠在了一起,所以我们不妨处理一下,其实就是如果x_1-y_1>x_2-y_2,那我就先杀1,否则先杀2。因为刚才为了方便表达,我们给y取了绝对值,还原回来以后,其实本质就是比较每条龙x+y的值,按照x+y从大到小排序。

总结一下,先杀正收益龙,再杀负收益龙,正收益龙按难度x从小到大排序,负收益龙按x+y从大到小排序。

【代码细节】
主要是排序部分,你可以把正收益龙和负收益龙分别存到两个数组中,也可以存到一个数组中,排序函数的写法就是:如果负收益龙在正收益龙前面,则交换;如果都是正收益龙,则按难度大小正序排序;如果都是负收益龙,则按x+y逆序排序。排好序就按游戏规则杀龙和氪金即可。

【坑点】
有可能会爆int,考虑20万条龙经验全是100万的情况。
代码:

//负收益时的思考方法 

#include<stdio.h>
#include<stdlib.h>
typedef struct change{
	long long int x;
	long long int y1;
	long long int y2;
}bianshen;
bianshen exp_get[1000000],exp_loss[1000000];

long long int i,l;
long long int A=0,money=0,t1,t2,m=0,n=0;

int cmp1(const void *a,const void *b){
	  struct change *c=(change *)a;
	  struct change *d=(change *)b;
	  return c->x-d->x;   
}
int cmp2(const void *a,const void *b){
	  struct change *c=(change *)a;
	  struct change *d=(change *)b;
	  return d->y2-c->y2;   
}


int main(){

	scanf("%lld\n",&l);
	for(i=0;i<l;i++){
		scanf("%lld %lld\n",&t1,&t2);
		if(t2>=0){
			exp_get[m].x=t1;
			exp_get[m].y1=t2;
			m++;
		}
		else{
			exp_loss[n].x=t1;
			exp_loss[n].y1=t2;
			exp_loss[n].y2=t1+t2;
			n++;
		}
	}
	//对 正收益处理 
	
	qsort(exp_get,m,sizeof(exp_get[0]),cmp1);
	
	for(i=0;i<m;i++){
		if(A>=exp_get[i].x){
			A+=exp_get[i].y1;
		}
		else{
			money+=exp_get[i].x-A;
			A=exp_get[i].x;
			A+=exp_get[i].y1;
		}
		
	}          
	//负收益 
	
	qsort(exp_loss,n,sizeof(exp_loss[0]),cmp2);
	
	for(i=0;i<n;i++){
		if(A>=exp_loss[i].x){
			A+=exp_loss[i].y1;
		}
		else{
			money+=exp_loss[i].x-A;
			A=exp_loss[i].x;
			A+=exp_loss[i].y1;
		}
	}
	printf("%lld\n",money);
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值