洛谷题单-【算法1-5】贪心

目录

0.简介

1.1 结构体排序-洛谷P1223 排队接水

1.2 结构体排序-洛谷P1803 凌乱的yyy / 线段覆盖

1.3 结构体排序-洛谷P1478 陶陶摘苹果(升级版)

1.4 结构体排序-洛谷P1208混合牛奶

1.5  小数的结构体排序-P2240 部分背包问题

2.1 巧解-洛谷 P3817小A的糖果

2.2 巧解 P5019 铺设道路

2.3 巧解-P1090合并果子

3.1双指针+排序 P1094纪念品分组

4、字符串+贪心 P1106 删数问题

5、排序+贪心  P4995 跳跳

6、高精度+结构体排序+贪心 P1080 [NOIP2012 提高组] 国王游戏


0.简介

贪心算法,是用计算机来模拟一个“贪心”的人做出决策的过程。这个人十分贪婪,每一步行动总是按某种指标选取最优的操作。而且他目光短浅,总是只看眼前,并不考虑以后可能造成的影响。

可想而知,并不是所有的时候贪心法都能获得最优解,所以一般使用贪心法的时候,都要确保自己能证明其正确性。

  • 适用范围

       贪心算法在有最优子结构的问题中尤为有效。最优子结构的意思是问题能够分解成子问题来解决,子问题的最优解能递推到最终问题的最优解

  • 证明方法

贪心算法有两种证明方法:反证法和归纳法。一般情况下,一道题只会用到其中的一种方法来证明。

1.反证法:如果交换方案中任意两个元素/相邻的两个元素后,答案不会变得更好,那么可以推定目前的解已经是最优解了。

2.归纳法:先算得出边界情况(例如 )的最优解 ,然后再证明:对于每个 , 都可以由  推导出结果。

  • 常见题型

在提高组难度以下的题目中,最常见的贪心有两种。

(1)“我们将 XXX 按照某某顺序排序,然后按某种顺序(例如从小到大)选择。”。

(2)“我们每次都取 XXX 中最大/小的东西,并更新 XXX;有时“XXX 中最大/小的东西“可以优化,比如用优先队列维护。

二者的区别在于一种是离线的,先处理后选择;一种是在线的,边处理边选择。

排序解法
       用排序法常见的情况是输入一个包含几个(一般一到两个)权值的数组,通过排序然后遍历模拟计算的方法求出最优值。

后悔解法
       思路是无论当前的选项是否最优都接受,然后进行比较,如果选择之后不是最优了,则反悔,舍弃掉这个选项;否则,正式接受。如此往复。

1.1 结构体排序-洛谷P1223 排队接水

oj:https://www.luogu.com.cn/problem/P1223

标签:贪心排序快速排序,快排    但是我用的是结构体排序

package 贪心;
/*思路:按接水时间升序排序时,总共等待的时间最小。
 * java结构体+排序
 */
import java.util.Arrays;
import java.util.Scanner;

public class P1223排队接水 {
	public static void main(String[] args) {
		Scanner in = new Scanner(System.in);
		int n=in.nextInt();
		Water[] w=new Water[n];
		int num;
		double t=0,sum=0;
		for(int i=0;i<n;i++) {
			num=in.nextInt();		
			w[i]=new Water(i+1,num);
		}
		Arrays.sort(w);
		for(int i=1;i<n;i++) {
			t+=w[i-1].num;//从第二人开始每个人的等待=之前的打水时间相加
			sum+=t;//总的等待时间
		}
		System.out.println();
		System.out.printf("%.2f",sum/n);
	}
	//实现Comparable接口中定义的compareTo抽象方法,按时间升序排序
	public static class Water implements Comparable<Water>{
		int num,id;	
		public Water(int id, int num) {
			super();
			this.num = num;
			this.id = id;
		}
		@Override
		public int compareTo(Water w) {
			return this.num-w.num;//this在前,时间升序
		}		
	}
}

1.2 结构体排序-洛谷P1803 凌乱的yyy / 线段覆盖

oj:https://www.luogu.com.cn/problem/P1803             标签:搜索贪心排序

package 贪心;

import java.util.Arrays;
import java.util.Scanner;

/*
显然放右端点最靠左的线段最好,从左向右放,右端点越小妨碍越少
其他线段放置按右端点排序,贪心放置线段,即能放就放*/

public class P1803凌乱的yyy_or线段覆盖 {
	public static void main(String[] args) {
		Scanner in = new Scanner(System.in);
		int n=in.nextInt();
		Time[] t=new Time[n];
		int a,b,right,i,ans=0;
		for(int i1=0;i1<n;i1++) {
			a=in.nextInt();
			b=in.nextInt();
			t[i1]=new Time(a,b);
		}
		Arrays.sort(t);
		for(i=0,right=0;i<n;i++) {
			if(t[i].l>=right) {
				ans++;
				right=t[i].r;
			}
		}
		System.out.println(ans);
	}
	///实现Comparable接口的抽象方法compareTo
	static class Time implements Comparable<Time>{
		int l,r;
		public Time(int l, int r) {
			super();
			this.l = l;
			this.r = r;
		}
		@Override
		public int compareTo(Time o) {
			return this.r-o.r;//按右端点升序
		}		
	}
}

1.3 结构体排序-洛谷P1478 陶陶摘苹果(升级版)

oj:https://www.luogu.com.cn/problem/P1478

标签:模拟贪心排序洛谷原创

package 贪心;

import java.util.Arrays;
import java.util.Scanner;

public class P1478陶陶摘苹果_升级版 {
	public static void main(String[] args) {
		Scanner in = new Scanner(System.in);
		int n=in.nextInt();
		int s=in.nextInt();
		int a=in.nextInt();
		int b=in.nextInt();
		int[] x=new int [n];
		int[] y=new int [n];
		Apple[] ap=new Apple[n];
		int m=0,ans=0;
		for(int i=0;i<n;i++) {
			x[i]=in.nextInt();
			y[i]=in.nextInt();
		}
		for(int i=0;i<n;i++) {
			if(a+b>=x[i]) {//如果够得着就加入到ap数组中
				ap[i]=new Apple(x[i], y[i]);
				m++;//app的需要排序的个数
			}
			else ap[i]=new Apple(281, 101); //定义的类必须给每个元素赋值,否则空指针异常
		}
		Arrays.sort(ap);
		for(int i=0;i<m;i++) {
			if(s>=ap[i].y) {//如果剩余的力气还够,就消耗力气
				s-=ap[i].y;
				ans++;//计数
			}
		}
		System.out.println(ans);
	}
	static class Apple implements Comparable<Apple>{
		int x,y;		
		public Apple(int x, int y) {
			super();
			this.x = x;
			this.y = y;
		}
		@Override
		public int compareTo(Apple o) {		
			return this.y-o.y;//力气升序
		}		
	}
}

1.4 结构体排序-洛谷P1208混合牛奶

oj:https://www.luogu.com.cn/problem/P1208

标签:贪心快速排序,快排背包USACO

package 贪心;
/*
 * 首先按单价升序排序,当需求量大于零时,需求量减去当前农民的牛奶量,费用加上当前农民的总费用
 * 当需求量小于等于零时,可能在上一个农民的牛奶买多了,
 * 所以就重新返回到上一个农民时的需求量和费用,一个个牛奶减去,需求量够了就退出循环
 */
import java.util.Arrays;
import java.util.Scanner;

public class P1208混合牛奶 {
	public static void main(String[] args) {
		Scanner in = new Scanner(System.in);
		int need=in.nextInt();
		int n=in.nextInt();
		Milk[] m=new Milk[n];
		int p,milk,ans=0;
		for(int i=0;i<n;i++) {
			p=in.nextInt();
			milk=in.nextInt();
			m[i]=new Milk(p, milk);
		}
		Arrays.sort(m);
		int flag=1;
		for(int i=0;i<n&flag==1;i++) {
			if(need>0) {
				need-=m[i].milk;	
				ans+=m[i].price*m[i].milk;
			}
			if(need<=0&&flag==1) {
				need=need+m[i].milk;//返回到上一个农民时的所需要量
				ans-=m[i].price*m[i].milk;;//返回到上一农民时交了的费用
				for(int j=0;j<m[i].milk&&flag==1;j++) {
					need--;
					ans+=m[i].price;
					if(need<=0)flag=0;
				}
			}
		}
		System.out.println(ans);
	}
	static class Milk implements Comparable<Milk>{
		int price,milk;
		public Milk(int price, int milk) {
			super();
			this.price = price;
			this.milk = milk;
		}
		@Override
		public int compareTo(Milk o) {
			return this.price-o.price;//单价升序
		}
		
	}
}

1.5  小数的结构体排序-P2240 部分背包问题

oj:https://www.luogu.com.cn/problem/P2240  标签:贪心

package 贪心;

import java.util.Arrays;
import java.util.Scanner;

public class P2240部分背包问题 {
	public static void main(String[] args) {
		Scanner in = new Scanner(System.in);
		int n=in.nextInt();
		int t=in.nextInt();
		Money[] mon=new Money[n];//定义的结构体
		int m,v;
		double sum=0;//拿走的金币总和
		for(int i=0;i<n;i++) {
			m=in.nextInt();
			v=in.nextInt();
			mon[i]=new Money(m, v);
		}
		Arrays.sort(mon);//性价比降序排序
		for(int i=0;i<n;i++) {
			if(mon[i].m<=t) {//第i堆金币能拿就都拿走
				sum+=mon[i].v;
				t-=mon[i].m;//背包还剩下多少空间
			}
			else {
				sum+=mon[i].v*t/(double)mon[i].m;//拿到背包满为止
				break;
			}	
		}
		System.out.printf("%.2f",sum);
	}
	static class Money implements Comparable<Money> {
		int m,v;
		double bi;
		public Money(int m, int v) {
			super();
			this.m = m;
			this.v = v;
		}
		public int compareTo(Money o) {
			//按性价比从高到低排序,为防止精度问题直接交叉相乘!!!
			return o.v*this.m-o.m*this.v;
		}	
	}
}

    注意:        //按性价比从高到低排序,为防止精度问题直接交叉相乘!!!
            return o.v*this.m-o.m*this.v;

2.1 巧解-洛谷 P3817小A的糖果

oj:https://www.luogu.com.cn/problem/P3817

标签:模拟贪心

package 贪心;
/*原则:要是大,就在后面一个吃,让下一组吃的少
 */
import java.util.Scanner;

public class P3817小A的糖果 {
	public static void main(String[] args) {
		Scanner in = new Scanner(System.in);
		int n=in.nextInt();
		int x=in.nextInt();
		int[] a=new int[n];
		long ans=0;
		for(int i=0;i<n;i++)
			a[i]=in.nextInt();
		for(int i=0;i<n-1;i++) {
			if(a[i]+a[i+1]>x) {
				ans+=a[i]+a[i+1]-x;
				a[i+1]=x-a[i];//因为两盒之和为x
			}
		}
		System.out.println(ans);
	}
}

2.2 巧解 P5019 铺设道路

oj:https://www.luogu.com.cn/problem/P5019

标签:贪心树状数组NOIp提高组2018

package 贪心;

import java.util.Scanner;
/*我们的贪心策略是:
若a[i]>a[i-1],计数器sum+=a[i]-a[i-1];
那么为什么这样贪心是对的呢?
贪心证明:
假设现在有一个坑,但旁边又有一个坑。你肯定会选择把两个同时减1;
那么小的坑肯定会被大的坑“带着”填掉。大的坑也会减少a[i]-a[i-1]的深度,可以说是“免费的”;
所以这样贪心是对的;*/
public class P5019铺设道路 {
	public static void main(String[] args) {
		Scanner in = new Scanner(System.in);
		int n=in.nextInt();
		int[] a=new int[n];
		int sum=0;
		for(int i=0;i<n;i++)
			a[i]=in.nextInt();
		for(int i=1;i<n;i++) {
			if(a[i]>a[i-1])
				sum+=a[i]-a[i-1];
		}
		System.out.println(a[0]+sum);
	}
}

2.3 巧解-P1090合并果子

oj:https://www.luogu.com.cn/problem/P1090 

标签:贪心二叉堆优先队列USACONOIp提高组高性能20042006

奈何不会二堆叉和优先队列o(╥﹏╥)o,只会用一些比较巧妙方法暴力解题_| ̄|●

package 贪心;

import java.util.Arrays;
import java.util.Scanner;

public class P1090合并果子 {
	public static void main(String[] args) {
		Scanner in = new Scanner(System.in);
		int n=in.nextInt();
		int[] a=new int[n];
		int ans=0;
		for(int i=0;i<n;i++)
			a[i]=in.nextInt();
		Arrays.sort(a);// 只sort一遍 
		int new_num,k;
		for(int i=0;i<n-1;i++) {
			// 算出结果,然后如果结果大于下一个,下一个就往前移 
			new_num=a[i]+a[i+1];
			k=i+2;//其他堆里的最小的堆
			while(k<n&&a[k]<new_num) {//当第k堆比当前的新堆小,Ps:k<n在&&前避免超数组范围
				a[k-1]=a[k]; //将小于新堆的元素往前移一位
				k++;  //指向下一个其他堆里的最小的堆
			}
			//此时第0到k-2堆已经排好序
			a[k-1]=new_num; //最后把新堆放到第k-1的位置
			ans+=new_num;//每次体力耗费值=新堆
		}
		System.out.println(ans);
	}
}

3.1双指针+排序 P1094纪念品分组

oj:https://www.luogu.com.cn/problem/P1094     标签:贪心排序NOIp,普及组2007

package 贪心;
//*读入之后先用sort排序,然后用两个指针一起向中间走,每次选择都尽
//可能的让当前状态下最大的和最小的分在一组,如果不行就最大的
//单独分一组,这样贪心下来就是最少分的组了。*/
import java.util.Arrays;
import java.util.Scanner;

public class P1094纪念品分组 {
	public static void main(String[] args) {
		Scanner in = new Scanner(System.in);
		int max=in.nextInt();
		int n=in.nextInt();
		int[] a=new int[n];
		for(int i=0;i<n;i++)
			a[i]=in.nextInt();
		Arrays.sort(a);
		int k=0,ans=0;
		for(int i=n-1;i>=0;i--) {			
			if(i<k)break;//break直接跳出循环,不再执行for循环里的后续语句
			ans++;
			if(a[i]+a[k]<=max)k++;
		}
		System.out.println(ans);
	}
}
//去年做的C++版while形式的双指针,感觉还是while清晰明了
//#include <bits/stdc++.h>
//using namespace std;
//int a[30005];
//int main(){
//	int n,w,ans=0,l,r;
//	cin>>w>>n;
//	for(int i=1;i<=n;i++)cin>>a[i];
//	sort(a+1,a+n+1);
//	l=1;r=n;
//	while(l<=r){
//		if(a[l]+a[r]<=w){
//			l++;r--;ans++;
//		}
//		else{
//			r--;ans++;
//		}
//	}
//	cout<<ans;
//	return 0;
//}

4、字符串+贪心 P1106 删数问题

oj:https://www.luogu.com.cn/problem/P1106 标签:字符串贪心

package 贪心;
//1   4  1  5  1   9
//小 大 小 大 小 大
//留 删 留删 留 留
//删掉的是“山峰”,也就是比后一个数大的数,且越靠前“山峰”越早删。
//大体思路也就一句话:删除靠前的“山峰”。
//另外,有几个坑不得不提:
//1.注意删除前导0(虽然它说每个数位都不为0,但是测试数据里面好像有这样的数据)。
//2.删过一个数记得长度len--。
//3.有多组数据(其实数组可以不清零,因为有len控制查找范围)。
//4.当把数删为0(见数据4)时,要输出0。
import java.util.Scanner;

public class P1106删数问题 {
	public static void main(String[] args) {
		Scanner in = new Scanner(System.in);
		String s=in.next();
		int n=in.nextInt();
		char[] c=s.toCharArray();
		int len=s.length();//结果的长度
		while(n>0) {
			n--;
			for(int i=0;i<len-1;i++) {
				if(c[i]>c[i+1]) {   //如果是山峰
					for(int j=i;j<len-1;j++) {    
						c[j]=c[j+1];//把山峰后的数字往前移动一位,覆盖了此山峰
					}
					break;   //找到一个山峰就跳出
				}
			}
			len--;
		}
		int num0=0;
		while(c[num0]=='0'&&num0<len)num0++;//处理前导0 
		if(num0==len)System.out.println(0);
		else {
			for(int i=num0;i<len;i++)
				System.out.print(c[i]);
		}
	}
}

5、排序+贪心  P4995 跳跳

oj:https://www.luogu.com.cn/problem/P4995 标签:排序,贪心

package 贪心;
//思路:在剩余的石头中最大和最小来回跳
import java.util.Arrays;
import java.util.Scanner;

public class P4995跳跳 {
	public static void main(String[] args) {
		Scanner in = new Scanner(System.in);
		int n=in.nextInt();
		int[] h=new int[n];
		for(int i=0;i<n;i++)
			h[i]=in.nextInt();
		Arrays.sort(h);
		int l=0,r=n-1;
		long sum=h[r]*h[r];
		while(l<r) {
			sum+=(h[r]-h[l])*(h[r]-h[l]);
			r--;
			sum+=(h[r]-h[l])*(h[r]-h[l]);
			l++;
		}	
		System.out.println(sum);
	}
}

6、高精度+结构体排序+贪心 P1080 [NOIP2012 提高组] 国王游戏

oj:https://www.luogu.com.cn/problem/P1080  标签:贪心高精排序NOIp提高组

package 贪心;

import java.math.BigInteger;
import java.util.Arrays;
import java.util.Scanner;

//正解= 贪心+高精.
//贪心证明 :
//设存在 相邻大臣A(l1,r1),B(l2,r2),A,B前左手乘积为Sum :
//   当A在B前时: 
//        则Ans1=max(Sum/r1,Sum*l1/r2) ;
//  当B在A前时: 
//        则Ans2=max(Sum/r2,Sum*l2/r1) ;
//  显然 Sum*l2/r1>Sum/r1  ; 
//     Sum*l1/r2>Sum/r2 ;
//  所以当 Sum*l2/r1>Sum*l1/r2  ans2大
//        即   l2*r2>l1*r1 时  A应在B前    
//  同理当 Sum*l2/r1<Sum*l1/r2  ans1大
//        即   l2*r2<l1*r1 时  B在A前	  
//所以,为了ans取到最小值,我们需要将l*r较小的放在前面
//以l*r为关键字排序即可
public class P1080国王游戏 {
	public static void main(String[] args) {
		Scanner in = new Scanner(System.in);
		int n=in.nextInt();
		DuiWu[] d=new DuiWu[n+1];
		BigInteger l,r;//左数,右数
		for(int i=0;i<=n;i++) {
			l=in.nextBigInteger();
			r=in.nextBigInteger();
			d[i]=new DuiWu(l, r);
		}
		BigInteger money=d[0].l;
		BigInteger max=BigInteger.ZERO;
		Arrays.sort(d,1,n+1);//国王必须站最前面,只排大臣
		for(int i=1;i<=n;i++) {	
			max=max.max(money.divide(d[i].r));
			if(i==n)break;
			money=money.multiply(d[i].l); //所有前面的人的左数成绩的乘积
		}
		if(max.compareTo(new BigInteger("1"))<0)System.out.println("1");
		else System.out.println(max);
	}
}
class DuiWu implements Comparable<DuiWu>{
	BigInteger l,r;
	public DuiWu(BigInteger l, BigInteger r) {
		super();
		this.l = l;
		this.r = r;
	}
	public int compareTo(DuiWu o) {
		return this.l.multiply(this.r).compareTo(o.l.multiply(o.r));
	}	
}

未完待续。。。

  • 6
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值