目录
1.2 结构体排序-洛谷P1803 凌乱的yyy / 线段覆盖
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
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
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
标签:贪心,二叉堆,优先队列,USACO,NOIp提高组,高性能,2004,2006
奈何不会二堆叉和优先队列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));
}
}
未完待续。。。