SDAU训练日志第四篇--------贪心算法(下)(2018年2月1日)

emmmmmmmm

昨天因为光学理论知识没做题觉着贪心好难,,

在证明一个问题可以用贪心解的证明那里持续懵逼。

今天A了几个简单的贪心题,发现也没想象中的那么难,

(可能是我太弱鸡了,还没接触到真正层面的贪心)

所谓贪心,本质上就是按照制定好的贪心策略按顺序取数,以局部最优形成全局最优,关键是如何制定最优的贪心策略。

比如今天的一个题:

均分纸牌(洛谷P1301) 

       有 N 堆纸牌,编号分别为 1,2,…, N。每堆上有若干张,但纸牌总数必为 N 的倍数。可以在任一堆上取若干张纸牌,然后移动。
移牌规则为:在编号为 1 堆上取的纸牌,只能移到编号为 2 的堆上;在编号为 N 的堆上取的纸牌,只能移到编号为 N-1 的堆上;其他堆上取的纸牌,可以移到相邻左边或右边的堆上。
现在要求找出一种移动方法,用最少的移动次数使每堆上纸牌数都一样多。
例如 N=4,4 堆纸牌数分别为:
①9②8③17④6
移动3次可达到目的:

从 ③ 取 4 张牌放到 ④ (9 8 13 10) -> 从 ③ 取 3 张牌放到 ②(9 11 10 10)-> 从 ② 取 1 张牌放到①(10 10 10 10)。

分析:简化问题处理,把每堆牌变成在0的上下波动,当牌堆的高度为0就代表不需要再处理了,如-1,-2,3我们把-1移到-2,然后-3移动到3上,就完成了。

注意:关键点是注意到减去平均数,要不然不好做

#include<iostream>
using namespace std;
int main()
{
    int n=0;
    cin>>n;
    int *card=new int [n];
    int temp=0,aver=0;
    for(int i=0;i<n;i++)
    {
        cin>>card[i];
        temp+=card[i];
    }
    aver=temp/n;
    for(int i=0;i<n;i++)
        card[i]-=aver;
    int out=0;
    for(int i=0;i<n;i++)
    {
        if(card[i]==0) continue;
        card[i+1]+=card[i];
        out++;
    }
    cout<<out<<endl;
    return 0;
}

P1094 纪念品分组
元旦快到了,校学生会让乐乐负责新年晚会的纪念品发放工作。为使得参加晚会的同学所获得 的纪念品价值相对均衡,他要把购来的纪念品根据价格进行分组,但每组最多只能包括两件纪念品, 并且每组纪念品的价格之和不能超过一个给定的整数。为了保证在尽量短的时间内发完所有纪念品,乐乐希望分组的数目最少。 你的任务是写一个程序,找出所有分组方案中分组数最少的一种,输出最少的分组数目。
输入输出格式
输入格式:
输入文件group.in包含n+2行:
第1行包括一个整数w,为每组纪念品价格之和的上上限。
第2行为一个整数n,表示购来的纪念品的总件数G
第3~n+2行每行包含一个正整数Pi (5 <= Pi <= w)表示所对应纪念品的价格。

输出格式:输出文件group.out仅一行,包含一个整数,即最少的分组数目。

#include<iostream>
#include<algorithm>
using namespace std;
int sum_value,ans=0;
int n,jinian[30005];

int main()
{
    int left=0,right=0,i=0;
    cin>>sum_value>>n;
    for(i=0;i<n;i++)
      cin>>jinian[i];
    sort(jinian,jinian+n);
    left=0;  right=n-1;
 while(left<=right)
    {
        if(jinian[left]+jinian[right]<=sum_value)
          left++,right--,ans++;
        else
          right--,ans++;
    }
    cout<<ans<<endl;
    return 0;
}

关键是尽量把最大的和最小的放在一起的这种贪心思想值得注意,如果 最大的和最小的放在一起超出范围,那么最大的和第二小的放在一起肯定也不行,这个时候把这个大的单独存一个,再找第二大的进行比较。

水的某博客的题:

问题描述:给定一个长度为m的区间,再给出n条线段的起点和终点(注意这里是闭区间),求最少使用多少条线段可以将整个区间完全覆盖

样例:
区间长度8,可选的覆盖线段[2,6],[1,4],[3,6],[3,7],[6,8],[2,4],[3,5]
解题过程:
1将每一个区间按照左端点递增顺序排列,排完序后为[1,4],[2,4],[2,6],[3,5],[3,6],[3,7],[6,8]
2设置一个变量表示已经覆盖到的区域。再剩下的线段中找出所有左端点小于等于当前已经覆盖到的区域的右端点的线段中,右端点最大的线段在加入,直到已经覆盖全部的区域
3过程:
假设第一步加入[1,4],那么下一步能够选择的有[2,6],[3,5],[3,6],[3,7],由于7最大,所以下一步选择[3,7],最后一步只能选择[6,8],这个时候刚好达到了8退出,所选区间为3
4贪心证明:
需要最少的线段进行覆盖,那么选取的线段必然要尽量长,而已经覆盖到的区域之前的地方已经无所谓了,(可以理解成所有的可以覆盖的左端点都是已经覆盖到的地方),那么真正能够使得线段更成的是右端点,左端点没有太大的意义,所以选择右端点来覆盖

贪心的几类问题:

1 , 最优装载问题,
     给出不同重量的物品,使得总重量不超过C,  贪心  从最轻的开始装起;

例如今天的:洛谷P1090合并果子

2, 部分背包问题
     n个物体,重wi,价值vi,每种物品可以装部分,在总重量不超过c的情况下,总价值尽量高;
     贪心  先装载 单位质量价值高的物品;注意这里的物品是可以进行分割的,否则,就要用到DP了;

洛谷P1208采购牛奶

3, 乘船问题
    n个人,重wi,每艘船承重C,最多载俩人,用最少的船装载所有的人
    贪心  用最轻的人和最重的人在一起,如果超重的话,只能让最重的人单独乘一艘船,一次进行类推下去,直到排完所有的人;本文的第二个例题。
 4,此外区间类问题(引用新浪微博)

1)完全区间问题:本文第三个例题。
2)区间选点问题  http://acm.hdu.edu.cn/showproblem.php?pid=4883

问题描述:

给定一个长度为m的区间,再给出n条线段和这n条线段需要满足的要求(要求是这n条线段上至少有的被选择的点的个数),问题是整个区间内最少选择几个点,使其满足每一条线段的要求.

样例:略
解题过程:将每个线段按照终点坐标进行递增排序,相同终点的前点坐标大的在前面,一个个将其满足
贪心证明:要想使得剩下的线段上选择的点最少,那么就应该尽量使得已经选择了的点尽量能在后面的线段中发挥作用,而我们是从左往右选择线段的,那么要使得选取的点能满足后面线段的要求,那么必须是从线段的有端点开始选点,那么问题(2)一样涉及到一个问题,如果是按照线段的左端点对线段进行排序的话,不知道右端点的话,每一条线段都不能对之前已经操作过的所有线段进行一个总结,那么这就同样不满足贪心算法的最优子结构性质了。

可以解决的实际问题:数轴上面有n个闭区间[a,b],取尽量少的点,使得每个区间内都至少有一个点(不同区间内含的点可以是同一个)

3)最大不相交覆盖

问题描述:

给定一个长度为m的区间,再给出n条线段的起点和终点(开区间和闭区间处理的方法是不同,这里以开区间为例),问题是从中选取尽量多的线段,使得每个线段都是独立的,就是不和其它有任何线段有相交的地方

样例:
区间长度8,可选的覆盖线段[2,6],[1,4],[3,6],[3,7],[6,8],[2,4],[3,5]
解题过程:
对线段的右端点进行升序排序,每加入一个线段,然后选择后面若干个(也有可能是一个)右端点相同的线段,选择左端点最大的那一条,如果加入以后不会跟之前的线段产生公共部分,那么就加入,否则就继续判断后面的线段
1排序:将每一个区间按右端点进行递增顺序排列,拍完序后为[1,4],[2,4],[2,6],[3,5],[3,6],[3,7],[6,8]
2第一步选取[2,4],发现后面只能加入[6,8],所以区间的个数为2

贪心证明:因 为需要尽量多的独立的线段,所以每个线段都尽可能的小,对于同一右端点,左端点越大,线段长度越小。那么为什么要对右端点进行排序呢?如果左端点进行排 序,那么右端点是多少并不知道,那么每一条线段都不能对之前所有的线段进行一个总结,那么这就明显不满足贪心的最优字结构了。


贪心的关键点还是贪心策略的选取!多刷刷题培养感觉吧。

先写到这吧,睡觉咯~~~(๑╹ヮ╹๑)ノ Studying makes me happy*✧⁺˚⁺ପ(๑・ω・)੭ु⁾⁾ 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值