贪心法

搜索算法和动态规划是在多种策略中选取最优解,贪心算法是遵循某种规则,不断地选取当前最优策略。 

 

一、硬币问题

有1元,5元,10元,50元,100元,500元的硬币各C1,C5,C10,C50,C100,C500枚。现在用这些硬币来支付A元,最少需要多少枚硬币。

策略是尽可能选取面额大的硬币

 1 int main()
 2 {
 3     int ans=0;
 4     int V[6]={1,5,10,50,100,500};
 5     int C[6];
 6     int A;
 7     cin>>A;
 8     for(int i=0;i<6;i++)
 9     {
10         cin>>C[i];
11     }
12     for(int i=5;i>=0;i--)
13     {
14         int t=min(A/V[i],C[i]);
15         A-=t*V[i];
16         ans+=t;
17     }
18     cout<<ans<<endl;
19     return 0;
20 }
View Code

 

二、区间问题

有n项工作。每项工作分别在si时间开始,在ti时间结束。对于每项工作,你都可以选择参与与否。如果选择了参与,那么自始至终都必须参与。此外,参与工作的时间段不能重复。

选择的策略是:在可选的工作中,每次都选取结束时间最早的工作。

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 const int max_n=100000;
 4 int n,s[max_n],t[max_n];
 5 pair<int,int> itv[max_n];
 6 int ans=0;
 7 int main()
 8 {
 9     cin>>n;
10     for(int i=0;i<n;i++)
11     {
12         cin>>s[i]>>t[i];
13         //对pair进行的是字典序比较
14         //为了让结束时间早的工作排在前面
15         itv[i].first=t[i];
16         itv[i].second=s[i];
17     }
18     sort(itv,itv+n);
19 
20     //t是所选工作的结束时间
21     int t=0;
22     for(int i=0;i<n;i++)
23     {
24         if(t<itv[i].second)
25         {
26             ans++;
27             t=itv[i].first;
28         }
29     }
30     cout<<ans<<endl;
31     return 0;
32 }
View Code

 

三、字典序最小问题

给定长度为n的字符串s,要构造一个长度为n的字符串t,起初,t是一个空串,随后反复进行如下操作。

1.从s的头部删除一个字符,加到t的尾部

2.从s的尾部删除一个字符,加到t的尾部

目标是要构造字典序尽可能小的字符串t。

选择的策略是:按照字典序比较s和将s反转后的s‘

如果s较小,就从s的开头取出一个文字,加到t的末尾

如果s’较小,就从s‘的开头取出一个文字,加到t的末尾。

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 const int max_n=2000;
 4 int n;
 5 char s[max_n+1];
 6 int main()
 7 {
 8     cin>>n;
 9     cin>>s;
10     int a=0,b=n-1;
11     while(a<=b)
12     {
13         //将从左起和右起的字符串比较
14         bool left=false;
15         for(int i=0;i+a<=b;i++)
16         {
17             if(s[a+i]<s[b-i])
18             {
19                 left=true;
20                 break;
21             }
22             else if(s[a+i]>s[b-i])
23             {
24                 left=false;
25                 break;
26             }
27         }
28         if(left) putchar(s[a++]);
29         else putchar(s[b--]);
30     }
31     putchar('\n');
32     return 0;
33 }
View Code

 

四、

直线上有n个点,点i的位置是xi。从这n个点中选择若干个,给他们加上标记。对每一个点,其距离为r以内的区域里必须有带有标记的点(自身也算)。至少要有多少点被加上标记。(poj3069)代码是求解大致流程,不完全符合题目。

策略是:首先选取最左边的点右侧距离最左点距离为R以内的最远点。标记后,从标记点+r的距离开始寻找“最左点”,重复操作。

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 const int max_n=2000;
 4 int x[max_n+1];
 5 int n,r;
 6 int main()
 7 {
 8     cin>>n>>r;
 9     for(int i=0;i<n;i++)
10     {
11         cin>>x[i];
12     }
13     sort(x,x+n);
14     int i=0,ans=0;
15     while(i<n)
16     {
17         //s是没有被覆盖的最左边的点的位置
18         int s=x[i++];
19         //一直前进到距离s大于r的点
20         while(i<n&&x[i]<=s+r) i++;
21         int p=x[i-1];
22         //一直前进道距离p大于r的店
23         while(i<n&&x[i]<=p+r) i++;
24         ans++;
25     }
26     cout<<ans<<endl;
27     return 0;
28 }
View Code

 

 

五、

农夫约翰为了修理栅栏,要将一块很长的木板切割成n块。准备切成的木板的长度为l1,l2,...,ln。未切割前木板的长度恰好为切割后木板长度的总和。每次切断木板时,需要的开销为这块木板的长度。求按要求切完的最小开销。只说明了大概意思。

分析如下:

切割的方法可以用二叉树来描述。但是我实在懒。所以就简单表述一下这颗树。木板起始长度为15.

                             15

                       7             8

                  3        4    5      3

                                       1     2

我猜应该能勉强看出这是一个二叉树。。。然后我们发现开销的合计为 : 所有叶子节点的权值*叶子节点的深度的总和。

即3*2+4*2+5*2+1*3+2*3=33。

此时的最佳切割方法首先应该有如下性质:最短的板与次短的板的节点应当是兄弟节点(对于最优解来说,最短的板应该是深度最大的叶子节点之一,所以与这个叶子节点同一深度的兄弟节点一定存在,由于同样是最深的叶子节点,所以应该对应于次短的板)。

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 const int max_n=20000;
 4 int l[max_n+1];
 5 int n;
 6 int ans=0;
 7 int main()
 8 {
 9     cin>>n;
10     for(int i=0;i<n;i++)
11     {
12         cin>>l[i];
13     }
14 
15     //计算到木板为1块为止
16     while(n>1)
17     {
18         //求出最短的板mii1和次短的板mii2
19         int mii1=0,mii2=1;
20         if(l[mii1]>l[mii2]) swap(mii1,mii2);
21         for(int i=2;i<n;i++)
22         {
23             if(l[i]<l[mii1])
24             {
25                 mii2=mii1;
26                 mii1=i;
27             }
28             else if(l[i]<l[mii2])
29             {
30                 mii2=i;
31             }
32         }
33 
34         //合并两板
35         int combine=l[mii1]+l[mii2];
36         ans+=combine;
37 
38         //mii1的位置放合并板,mii2的位置放最后一块板
39         if(mii1==n-1) swap(mii1,mii2);
40         l[mii1]=combine;
41         l[mii2]=l[n-1];
42         n--;
43     }
44     cout<<ans<<endl;
45     return 0;
46 }
View Code

 

然后可以联想到霍夫曼编码(当码字长度为整数情况时最优的编码方案)。频度较高的字母对应较短的01序列,频度较低的字母对应较长的01序列。将木板换成字符,长度换成频度可以类比。

posted @ 2017-02-21 19:05  docyard  阅读(607)  评论(0编辑  收藏  举报