贪心算法-例题
@前言@
Ok又是我长期不更博客的怠惰之人~
那么这次给大家带来的是贪心算法的系列例题以及作者-我的感想
好的废话不多说,直接进入正题=>=>
@概念@
@介绍@
【以下是官方介绍】
贪心算法(又称贪婪算法)是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的是在某种意义上的局部最优解。
贪心算法不是对所有问题都能得到整体最优解,关键是贪心策略的选择,选择的贪心策略必须具备无后效性,即某个状态以前的过程不会影响以后的状态,只与当前状态有关。
【引用自百度百科】
嗯,这解释很官方
但是如果只会死记硬背的话就没意思了~
【以下来自作者的理解】
贪心算法,就像是要到达一个地方,有很多岔路横生的、曲折的道路。你不需要理会横生的支路,因为只是朝着终点的方向一直走,不管路有没有断。
嗯,也就是说明,贪心算法不一定得到正确的答案(关键)(快做笔记)。
与其说贪心是一种算法,不妨说贪心是一种思想、策略——鼠目寸光,眼光短浅,没有长远之见【啊喂喂为什么都是贬义词】。所以在判断一个问题是否能贪心的方法是很 多变 的【表示甚至有时候你用了贪心自己都不知道【可能是作者自己的错QAQ】】。
【以下摘自老师课件】
贪心算法的步骤:
(1)把求解的问题分成若干个子问题。
(2)对每一子问题求解,得到子问题的局部最优解。
(3)把子问题的解局部最优解合成原来解问题的一个解。
@适用范围@
-(1)判断该题是否适合于用贪心策略求解
这个应该很容易理解,毕竟每个算法都有自己的适用范围
你应该不可能拿贪心解决【A+B Problem】,是吧
【注意!】虽然我说着适用贪心的判断好似很简单,其实不然。贪心的应用千万要严格证明出来!不然你怎么得到的0分都不知道
-(2)决定如何选择贪心标准,以得到问题的最优解
举个栗子:【部分背包问题】
现有一个口袋,口袋至多只能装重量为w的金属。有s个种类的金属, 每种金属重量不同,分别为n1, n2, … , ns,同时每个种类的金属总的价值也不同,分别为v1,v2, …, vs。问最多能带走价值多少的金属。注意到金属是可以被任意分割的,并且金属的价值和其重量成正比。
一个简单的贪心标准:总价值的大小
但是这对不对呢?以我们生活的小常识就可知:单价才可以决定物品真正的价值。所以显然贪心标准选择错误。
【再次注意!】依然的,我们选择了一个通俗易懂的栗子。但是你永远不知道 这个错误的“总价值”会在哪个困难的题目把你迷得团团转,所以不要轻视!
@一些典型的结构@
贪心和DP相似点之一就是它也有着特殊的问题结构,可以在你迷茫时点中你的思路。那么:
【以下摘自老师课件】
1.贪心选择性质:算法中每一步选择都是当前看似最佳的选择,这种选择依赖于已做出的选择,但不依赖于未做的选择。
2.最优子结构性质:算法中每一次都取得了最优解(即局部最优解),要保证最后的结果最优,则必须满足全局最优解包含局部最优解。
【不知道你们看懂没有但是……还算是有用的】
但并不是所有具有最优子结构的问题都可以用贪心策略求解。因为贪心往往是盲目的,需要使用更理性的方法——动态规划【那就不是我们讨论的内容了~】
@例题解析@
<1>装箱问题-【唉?这里还可以装!】
此处可测试你的代码
描述
一个工厂制造的产品形状都是长方体,它们的高度都是h,长和宽都相等,一共有六个型号,他们的长宽分别为1*1, 2*2, 3*3, 4*4, 5*5, 6*6。这些产品通常使用一个 6*6*h 的长方体包裹包装然后邮寄给客户。因为邮费很贵,所以工厂要想方设法的减小每个订单运送时的包裹数量。他们很需要有一个好的程序帮他们解决这个问题从而节省费用。现在这个程序由你来设计。
输入
输入文件包括几行,每一行代表一个订单。每个订单里的一行包括六个整数,中间用空格隔开,分别为1*1至6*6这六种产品的数量。输入文件将以6个0组成的一行结尾。
输出
除了输入的最后一行6个0以外,输入文件里每一行对应着输出文件的一行,每一行输出一个整数代表对应的订单所需的最小包裹数。
样例输入
0 0 4 0 0 1
7 5 1 0 0 0
0 0 0 0 0 0
样例输出
2
1
@解析@
抓住两点:
<1>包裹空间装满,才可以最优√
<2>先装大箱后装小箱,用小箱填补大箱的空位,才可以装满√
@深度解析+代码
OK咱先把框架弄出来
#include<cstdio>
#include<cmath>
int a[10];
int main()
{
while(true)
{
int ans=0,S=0;
for(int i=1;i<=6;i++)
{
scanf("%d",&a[i]);
S+=a[i];
}
if(S==0) return 0;//用来简便判断是否已结束程序
//显然,当且仅当6个都为0时,满足if
/*
此
处
是
重
点
代
码
*/
printf("%d\n",ans);
}
}
重点的代码,我们按照解析的思路来:
<1>装6*6的箱子:这一步很显然,因为6*6的箱子直接将空间占满了。
代码:
int a[10];
...
{
ans+=a[6];
}//6*6
<2>装5*5的箱子:此时剩下6*6-5*5的空间即11个空间,鉴于装完5*5的箱子之后只能装1*1,所以用1*1的箱子尽可能地去装。
代码:
int a[10];
...
{
ans+=a[5];
a[1]-=min(11*a[5],a[1]);//这就是尽可能的意思
/*稍稍解释一下,这条语句的意思是:
<1>当11*a[5]<=a[1]时,即1*1能够装满
a[1]就会剩下a[1]-11*a[5]
<2>当11*a[5]>a[1]时,即1*1装不满
尽量装后剩下a[1]-a[1]即0个
下面类似的语句同理*/
}//5*5
<3>装4*4的箱子:这个稍稍有点麻烦咯。我们先考虑用2*2的填空,共需要(6*6-4*4)/4即5个。然后依然尽可能地装。如果装完了呢?这时再用1*1的去尽可能地装,即实现了目的。
代码:
int a[10];
...
{
int a[10];
...
ans+=a[4];
if(5*a[4]>a[2])//如果2*2会装完
a[1]-=min((a[4]*5-a[2])*4,a[1]);
//剩下a[4]*5-a[2]个本应该是2*2的空间
//于是剩下((a[4]*5-a[2])*4个1*1的空间
//“尽可能”地装吧
a[2]-=min(5*a[4],a[2]);
}//4*4
<4>装3*3的箱子:好的我们来到最困难的一部分。之所以说最困难,因为装3*3要考虑3*3+2*2+1*1,想想就头大。
没事的,我们一步步稳重地慢慢解
根据我们的解析,我们要先装大箱子。很幸运地,我们发现4个3*3可以不留缝的合成6*6的空间!也就是说我们的混装部分只有一个包裹,这为我们减轻了许多困难。
下一步,装2*2的箱子。不同的装法如图所示
鉴于2*2箱子的数量实在太麻烦了,我们可以写一个映射函数【或者数组】来表明不同数量的3*3箱子应该对应多少2*2箱子的数量,然后尽可能地装
下一步就较简单咯,用1*1尽可能地去装剩余的空间
代码:
int a[10];
int f(int x)
{
if(x==0) return 0;
if(x==1) return 5;
if(x==2) return 3;
if(x==3) return 1;
}
...
{
ans+=ceil(a[3]/4.0);//每4个一包裹,不足4个向上取整
a[3]=a[3]%4;//混装的3*3箱子数量
if(a[3]!=0)//如果等于0就没有剩余的空间~
{
a[1]-=min(36-a[3]*9-min(f(a[3]),a[2])*4,a[1]);
//36-a[3]*9-min(f(a[3]),a[2])*4这一串表示剩下的空间
//36-总空间,a[3]*9-3*3箱子占的空间
//min(f(a[3]),a[2])-所需2*2箱子的数量
a[2]-=min(f(a[3]),a[2]);
}
}//3*3
<5>装2*2的箱子:简单啦,直接上代码好了:
int a[10];
...
{
ans+=ceil(a[2]/9.0);
a[2]=a[2]%9;
if(a[2]!=0)
a[1]-=min(36-a[2]*4,a[1]);
}//2*2
<6>装1*1的箱子:
int a[10];
...
{
ans+=ceil(a[1]/36.0);
}//2*2
然后,再把这些残片组合起来就是——
AC代码!
#include<cstdio>
#include<cmath>
int a[10];
int min(int x,int y)
{
if(x<y) return x;
else return y;
}
int f(int x)
{
if(x==0) return 0;
if(x==1) return 5;
if(x==2) return 3;
if(x==3) return 1;
}
int main()
{
while(true)
{
int ans=0,S=0;
for(int i=1;i<=6;i++)
{
scanf("%d",&a[i]);
S+=a[i];
}
if(S==0) return 0;
{
ans+=a[6];
}//6*6
{
ans+=a[5];
a[1]-=min(11*a[5],a[1]);
}//5*5
{
ans+=a[4];
if(5*a[4]>a[2])
a[1]-=min((a[4]*5-a[2])*4,a[1]);
a[2]-=min(5*a[4],a[2]);
}//4*4
{
ans+=ceil(a[3]/4.0);
a[3]=a[3]%4;
if(a[3]!=0)
{
a[1]-=min(36-a[3]*9-min(f(a[3]),a[2])*4,a[1]);
a[2]-=min(f(a[3]),a[2]);
}
}//3*3
{
ans+=ceil(a[2]/9.0);
a[2]=a[2]%9;
if(a[2]!=0)
a[1]-=min(36-a[2]*4,a[1]);
}//2*2
{
ans+=ceil(a[1]/36.0);
}//1*1
printf("%d\n",ans);
}
}
@小结@
此题在贪心中算是简单的一类。思路本身不复杂,只是说代码量很大,容易将一些细节弄错。总体来说,明确贪心标准,问题就迎刃而解了!
<2>区间系列问题-【交叉、插点、覆盖、后而贪心】
贪心算法->区间:总共有3类问题,都是区间相关+贪心策略
1-选择不相交区间问题
2-区间选点问题
3-区间覆盖问题
我们一个一个来介绍,不必着急
1.选择不相交区间问题
首先看一下问题的模型:
给定n个半开区间[ai, bi),选择尽量多个区间,使得这些区间两两没有公共点。
描述得很简单,也很明了,甚至明了到无从下手。
先从贪心的结构——最优子结构开始讨论较为简便。在部分数轴[S,T]之间,假设区间A是最优解中的最左区间,则A的右端点R到T之间即[R,T]之间又形成了一个新的子问题。我们既然要最多——就要贪心,让[R,T]尽可能的长。
贪心标准:循环选择区间[L,R)使得在[S,T]中[R,T]最长
然后再来看看实现方法。
区间[R,T]中,T是固定右端点,则当R是所有区间的最左边时可以保证整个区间最长。即:对区间以【右端点】为关键字排序,使左边【小】的在前,然后再依次扫描。
关键部分代码:
struct Node{
int Begin,End;
}a[MN+5];
int Tot=0,Start=0;
...
sort(a+1,a+n+1,cmp);
for(int i=1;i<=n;i++)
if(a[i].Begin>=Start)
{
Start=a[i].End;
Tot++;
}
【正确性证明】我们按照b1<=b2<=b3…的方式排序【bi是右端点哦~题目中的描述不知道大家忘没忘】,假设我们不选最左边的b1,而是选择了bi。如果区间1与区间i相交,则将bi->b1,总数不变,且不影响后面的结果;否则,加入b1后总数+1,且不影响后面的结果。
2.区间选点问题
依然地,先上模型:
给定n个闭区间[ai, bi],在数轴上选尽量少的点,使得每个区间内都至少有一个点(不同区间内含的点可以是同一个)。
通过读题我们可以直观地给出一个思路:尽量在区间的相交部分上选点
【为了以防大家的思路跑偏,先稍稍提醒一下,我们的操作对象应该是区间而不是整条数轴】
先简化为两个区间相交的问题:若[a1,b1]与[a2,b2]有相交(b1<=b2),则相交部分的k可以使得a1<=k<=b1且a2<=k<=b2。合起来就是(a1&&a2)<=k<=b1<=b2。观察发现b1一定是满足的【即b1一定在相交部分】,也可以证明出。
证明:从(a1&&a2)<=k<=b1<=b2,若k=b1,得证;若k
struct Node{
int Begin,End;
}a[MN+5];
bool b[MX+5]
...
sort(a+1,a+n+1,cmp);
for(int i=1;i<=n;i++)
{
bool flag=false;
for(int j=a[i].Begin;j<=a[i].End;j++)
if(b[j])
{
flag=true;
break;
}//如果区间里已经存在了一个点
if(flag) concinue;
b[a[i].End]=true;
}
2.*区间选点问题-应用
【NOIP2010模拟】种树
题目描述
一条街的一边有几座房子。因为环保原因居民想要在路边种些树。路边的地区被分割成块,并被编号成1..N。每个部分为一个单位尺寸大小并最多可种一棵树。每个居民想在门前种些树并指定了三个号码B,E,T。这三个数表示该居民想在B和E之间最少种T棵树。当然,B≤E,居民必须记住在指定区不能种多于区域地块数的树,所以T≤E-B+l。居民们想种树的各自区域可以交叉。你的任务是求出能满足所有要求的最少的树的数量。 写一个程序计算最少要种树的数量
输入
一行包含数据N,区域的个数(0<N≤30000); 第二行包含H,房子的数目(0<H≤5000); 下面的H行描述居民们的需要:B E T,0<B≤E≤30000,T≤E-B+1。
输出
输出为满足所有要求的最少树的数量。
样例输入
9 4
1 4 2
4 6 2
8 9 2
3 5 2
样例输出
5
看上去的确和区间选点很相似——但请注意!题目中需要树的数量是不定的,而不是原型中的【1】。
那怎么呢?难道就不能借用区间选点思想了吗?
当然要借用!只不过需要小改一番
贪心标准:从区间的右端点起从右往左选点,直到数量足够
证明也很容易,将上面的正面稍微改改即可。
代码【Duang~】:
#include<cstdio>
#include<algorithm>
using namespace std;
struct Node{
int Begin,End,WM;
}a[5005];
bool Vis[3005];
bool cmp(Node a,Node b)
{
if(a.End<b.End) return 1;
else return 0;
}
int main()
{
int ans=0,n,h;
scanf("%d %d",&n,&h);
for(int i=1;i<=h;i++)
scanf("%d %d %d",&a[i].Begin,&a[i].End,&a[i].WM);
sort(a+1,a+h+1,cmp);
for(int i=1;i<=h;i++)
{
for(int j=a[i].Begin;j<=a[i].End;j++)
{
if(a[i].WM<=0) break;
if(Vis[j]) a[i].WM--;
}
for(int j=a[i].End;j>=a[i].Begin;j--)
{
if(a[i].WM<=0) break;
if(!Vis[j])
{
Vis[j]=true;
a[i].WM--;
}
}
}
for(int i=1;i<=n;i++)
if(Vis[i]) ans++;
printf("%d\n",ans);
}
3.区间覆盖问题
先看原型:
给n个闭区间[ai,bi],选择尽量少的区间覆盖一条指定线段[s,t]。
嗯。
从思考的角度来说,这道题与例<1>较为相近。即:我们选择一个包含左端点s的区间[L,R],剩下[R,t]这一条等待覆盖,所以拥有最优子结构。那么我们要贪心的就是:
贪心标准:循环选择包含线段左端点s的区间[L,R]使得[R,t]最短
虽然思路很清晰,但是真正实现起来不易,尤其是当某些题数据大到必须O(n)才能过,这个时候就需要我们好好去想想代码怎么写了。首先考虑包含s,则我们需要以【左端点】为关键字排序。然后考虑最短,可以用Max变量记录最大的R。如果区间i已经不包含s,则它以后的也都不包含,这时就可以更新s为Max。然后从i再开始【i以前的都不可能是最优解】,依次直到s>=t,覆盖完毕。
还有就是要考虑无解的情况。如果没有一个区间能覆盖s,则无解。
struct Node{
int Begin,End;
}a[MN+5];
bool flag=false;
int ans=0;
...
for(int i=1;i<=n;i++)
{
if(a[i].Begin>S&&flag==true)
{
S=Max;
ans++;
flag=false;
if(S>=T) break;
}
if(a[i].Begin>S&&flag==false)
{
printf("No Solution\n");
return 0;
}
Max=max(Max,a[i].End);
flag=true; //有区间可以覆盖s
}
@小结@
区间问题也算是贪心的一个经典应用。对于一开始接触的人可能是一脸懵,但是如果经过冷静地分析,从贪心的结构入手,依靠点小小的直觉,也就发现解法并非很困难。区间问题不只这最简单的三种,其的变式很多,但都逃不过贪心的范畴:排序,子问题求解。
<3>背包0-1-【不是所有问题都叫贪心】
@题目描述@
有 n 件物品, 每件物品有一个价值和一个重量,分别记为: v1,v2, …vn与w1,w2, …wn 其中所有的 重量wi 均为整数。 现有一个背包,其最大载重量为W,要求从这n件物品中任取若干件(这些物品要么被装入要么被留下)。问背包中装入哪些物品可使得所装物品的价值和最大?
这道题与另一道贪心可以做的背包问题——【部分背包】都同属于背包问题。但,究竟这道题我们是否能故技重施呢?
@贪心标准-错误@
如果能贪心,这里的贪心标准应该是什么呢?重量?价值?还是单位价值?
显然重量是不靠谱的,假设N=2,背包最大载重量是100,两种物品A、B的重量wi分别为1,100,其对应的价值vi分别为1,100。一个反例就被构造出来。【毕竟重量连部分背包都过不了】
其他两个标准也可构造反例。这里我们假设N=3,背包最大载重量是100,三种物品A、B、C的重量分别是45,50,70,其对应的总价值分别是90、100、150。情况A是贪心,情况B是动规,显然情况B是正解。
那么,这道背包问题真的就不能贪心了吗?
是的。
不管怎么选择贪心标准,总会被举出反例,这就是这道题。
@想说明的东西@
为什么我要在一个贪心的例题中加入一道不是贪心的东西?因为,我想说:尽管证明出问题能使用贪心是个很方便的途径,但如果在一道不是贪心的题目上耗费时间用贪心,便会得不偿失。理智地使用更全面的动态规划,才能稳扎稳打。
贪,而不得,反失。
<4>Huffman编码-【合并,压缩,再压缩】
@编码简介@
编码是信息从一种形式或格式转换为另一种形式的过程
我们通常用的文字【包括汉字与字母】都不能直接在电脑中存储,电脑当中只能存储一系列的0-1串。那么,我们就需要将阅读的【信息-文字】->电脑的【信息-01串】,这一过程就叫编码。
举个栗子:我们平常所用到的ASCII码就属于已经将【字母】这种信息编为了0-1串的码。我们输入法中的【汉字】对应的信息也是一种被编为0-1串的码。
这里就会涉及一个问题:如果我所传达的信息过长,那么系统会消耗过多的空间。这个时候,我们极其希望有一种能够压缩数据的方法,减少系统空间的消耗。
若是针对某一段信息,我选择给频率高的字母编短码,给频率低的字母编长码,就可以大大减少我们所消耗的空间。像这样给一组信息的每一个单位信息编长度不一的码,我们称为变长码。
相对的,有定长码一称。
举个栗子:
定长码所需空间=(
变长码所需空间=(
少了近1/4的空间。
当数据量大到爆炸时,这1/4的空间可不只一点两点。所以,将编码方式改成变长码是很重要。
那么,我们将注意转到变长码上来吧。看看所谓的最优编码是否真正存在。
@二叉树编码@
如果你有想到0-1串与二叉树的联系,那么恭喜你,离成功又近了一步!
我们将根结点向左子树的路径记为0,向右子树的路径记为1。那么,当从根结点走到某一结点时就会产生一个0-1串——若将要编码的信息放在结点上,不正就对应一种编码规则吗?
这里我们在引入前缀码的概念:
前缀:设a=b1b2…bn,bi∈{0,1}是一个0-1序列(符号串)。序列b= b1b2…bi >(1 i n)称为a的前缀。
.例如,设a=010, 则, 0, 01 ,010都是a的前缀.
前缀码: 设Q ={a1, a2, …, am}是一个0~1序列集合 . 如果Q中没有一个序列是另一个序列的前缀 , 则称Q为前缀码.
例如,{0,10,110}就是一个前缀码,而{0,10,101}就不是前缀码。
举个栗子:假设一种编码规则中a=101,b=10,c=100,d=00【注意到b是a的前缀】。那么【10100】到底意味着”ad”还是”bc”呢?出现了矛盾。
可证:前缀码不会出现矛盾。
将前缀码对应到二叉树上,会发现前缀码的意义就是将信息放到叶结点上。我们再将叶结点加上权值【看到上图了吗】,使权值=叶结点上信息的出现频率。那么,让我们再向前,大大地迈一步吧!
@最优二叉树-问题解决@
一个简单的引入:我们定义一个函数
相应地,在二叉树编码中,
让我们直接走向历史的先哲为我们锻造好的路途,省去弯曲的歧路,直接引出答案吧:
给定
n 个权值作为n 个叶子结点,构造一棵二叉树T ,若B(T) 达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman Tree)。假设有n个权值,则构造出的哈夫曼树有n个叶子结点。 n个权值分别设为 w1、w2、…、wn,则哈夫曼树的构造规则为:
(1) 将w1、w2、…,wn看成是有n 棵树的森林(每棵树仅有一个结点);
(2) 在森林中选出两个根结点的权值最小的树合并,作为一棵新树的左、右子树,且新树的根结点权值为其左、右子树根结点权值之和;
(3)从森林中删除选取的两棵树,并将新树加入森林;
(4)重复(2)、(3)步,直到森林中只剩一棵树为止,该树即为所求得的哈夫曼树。利用哈夫曼树来设计二进制的前缀编码,既满足前缀编码的条件,又保证编码总长最短。
【点我!查看百科原址】
也就是说,我们每次只【鼠目寸光】地选择最小的频率,把它放在最深的一层。
符合我们变长码的目的:给频率高的字母编短码,给频率低的字母编长码。
但是,这么草率的贪心选择,足够正确吗?
@贪心标准-正确性证明@
【注意!以下内容可能过于枯燥且无味!请小心食用!】
【以下证明来自《算法导论》】:
引理1:若
【证明】:令
现在我们通过交换结点
所以
同理,交换结点
原命题得证。
引理2:设
【证明】:先用
因为
用反证法。假设最优生成树为
与命题中的
原命题得证。
于是最优生成树的构造一定成立。
【没错我就是复制隔壁我自己的博客233】
@代码@
#include<cstdio>
#include<cstring>
struct Node{
int value,num;
}heap[55];
int left[105],right[105],pre[105];
int heap_len,ans[55];
char ch[55];
void swap(Node &a,Node &b)
{
Node t=a;a=b;b=t;
}
void insert(Node p)
{
heap[++heap_len]=p;
int k=heap_len;
while(k!=1&&heap[k/2].value>heap[k].value)
{
swap(heap[k/2],heap[k]);
k/=2;
}
}
Node top()
{
Node p=heap[1];
swap(heap[1],heap[heap_len]);
heap[heap_len].value=heap[heap_len].num=0;
heap_len--;
int k=1;
while(k*2<=heap_len)
{
if(heap[k].value>heap[k*2].value)
{
if(k*2+1<=heap_len&&heap[k*2].value>heap[k*2+1].value)
{
swap(heap[k],heap[k*2+1]);
k=k*2+1;
}
else
{
swap(heap[k],heap[k*2]);
k=k*2;
}
}
else if(k*2+1<=heap_len&&heap[k].value>heap[k*2+1].value)
{
swap(heap[k],heap[k*2+1]);
k=k*2+1;
}
else break;
}
return p;
}
void Print(int root,int len)
{
if(left[root]==0&&right[root]==0)
{
printf("%c->",ch[root]);
for(int i=1;i<=len;i++)
printf("%d",ans[i]);
printf("\n");
return ;
}
Print(left[root],len+1);
ans[len+1]=1;
Print(right[root],len+1);
}
int main()
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
int x;char c;Node p;
scanf("\n%c %d",&c,&x);
p.num=i;p.value=x;
ch[i]=c;
insert(p);
}
for(int i=1;i<n;i++)
{
Node a=top();
Node b=top();
Node c;
c.value=a.value+b.value;
c.num=n+i;
pre[a.num]=pre[b.num]=c.num;
left[c.num]=a.num;right[c.num]=b.num;
insert(c);
}
int root=0;
for(int i=1;i<2*n;i++)
if(pre[i]==0) root=i;
Print(root,0);
}
读入n,再读入n行,每行【需要编码的字母 频率】。
输出n行,每行【需要编码的字母->编码结果】。
@结束语@
恩。
贪心是一种常用的思想,用于求解最优化问题,它总是考虑眼前的最优化,而不是全局的最优化。但对于某些问题,贪心却总能找到正确的途径。贪心和动规是分不开的,可以这么说:动规走完了所有可能的途径,而贪心只走了一种途径走到底。
对于竞赛来讲,如果一个最优化问题不符合动规的【无后效性】,或者数据量过大不宜用动规,那么贪心是一种不错的考虑方向。
我所选取的4道题,各有各的道理。<1>是一个贪心的简单应用;<2>是用贪心思想解题的一个模型/类;<3>深刻说明了贪心的代价——如果不宜贪心,那么就会:贪,而不得,反失;<4>则是选取了贪心的一个漂亮的理论:哈夫曼树,许多的题都可以使用这个理论解题。
其实吧,这里应该还有一个<5>用来揭示贪心与拟阵的联系,可惜我学识疏浅,无法向大家交代一个我自己都无法完全理解的东西。如果大家有兴趣,可以另搜搜“拟阵 贪心”的博客,我只能告退了。
有的时候,贪心不是用来解题而是用来优化其他的算法。这时候的贪心可就真是一种【思想】而不是一种【算法】。大家也不要被【贪心->算法】这一想法禁锢着。
毕竟,在现实中,【贪婪】的思想也会在脑中残存。
End
就是这样,新的一天里,也请多多关照哦(ノω<。)ノ))☆.。~