-
貪心算法
思想:什么是貪心算法,什么算得上是貪心
貪心算法(又稱貪婪算法)是指,在對問題求解時,總是做出在當前看來是最好的選擇。也就是說,不從整體最優上加以考慮,只做出在某種意義上的局部最優解。貪心算法不是對所有問題都能得到整體最優解,關鍵是貪心策略的選擇,選擇的貪心策略必須具備無后效性,即某個狀態以前的過程不會影響以后的狀態,只與當前狀態有關。
例題:
-
最少硬幣問題 有1、2、5、10、20、50、100七種面值的硬幣,要支付指定的金額,問怎么支付所用的硬幣個數最少
策略:緊着最大分值換。
-
最大斜率問題 ,給出n個點的坐標(笛卡爾坐標) 求(A[i]-A[j])/(i-j)最大
策略:相鄰的坐標中找到最大斜率
-
區間選取(會場安排問題),給一個大區間l,r然后給你n個區間,最最多多少個區間沒有重復部分
例子:
學校的小禮堂每天都會有許多活動,有時間這些活動的計划時間會發生沖突,需要選擇出一些活動進行舉辦。小劉的工作就是安排學校小禮堂的活動,每個時間最多安排一個活動。現在小劉有一些活動計划的時間表,他想盡可能的安排更多的活動,請問他該如何安排。
輸入:
第一行是一個整型數m(m<100)表示共有m組測試數據。
每組測試數據的第一行是一個整數n(1<n<10000)表示該測試數據共有n個活動。
隨后的n行,每行有兩個正整數Bi,Ei(0<=Bi,Ei<10000),分別表示第i個活動的起始與結束時間(Bi<=Ei)輸出:
對於每一組輸入,輸出最多能夠安排的活動數量。
策略:每選一個之后能給后面的留更多的時間(效果:按結束時間排序)
那么第一個時,肯定選此時能選的結束時間最早的,選其他的話給后面留的時間都比前者小,所以咱們選的第一個肯定沒錯,就是此時能選的結束時間最早的,然后選第二個時,也是選可選時間中結束最早的,這樣保證有其最優解,歸納起來激就是,每個根據當前可用時間,選取一個結束時間最早的,做為下一個會場的安排,
題目鏈接:http://acm.nyist.cf/problem/14
#include<stdio.h> #include<algorithm> using namespace std; const int maxn=10010; struct Node{ int beg,end; }node[maxn]; bool cmp(Node a,Node b) { return a.end<b.end; } int main() { int t,n,ans; scanf("%d",&t); while(t--) { scanf("%d",&n); for(int i=0;i<n;++i)//輸入區間 並處理 { scanf("%d %d",&node[i].beg,&node[i].end); node[i].end++;//將區間變為左閉右開 } sort(node,node+n,cmp);//將區間按右端點排序,右端點小的在前面 ans=0; int pos=0;//初始化 //pos意為上一個選取的活動結束的位置,若果beg>=pos就可以安排 for(int i=0;i<n;++i) { if(node[i].beg>=pos) { ++ans; pos=node[i].end; } } printf("%d\n",ans); } }
-
區間選點問題,n個閉區間[ai,bi],讓他取盡量少的點,使得每個閉區間內至少有一個點。
輸入:
n個閉區間,
輸出:
最少用幾個點,把每個區間都包含一個點
策略:讓這個點出現在一個沒有點的區間上,盡可能覆蓋多的區間的地方**(效果:按結束處排序)**
首先為了將最左邊的一個區間覆蓋,(按結束排序即可)那么第一個點必須在第一個區間上,那么在區間上哪呢?為了讓這個點讓更多的區間的區間碰到,讓這個點最靠右,這樣的話能保證這個點覆蓋的地方最多,然后一直往后遍歷,直到一個區間不在這個點上時,為了讓這個區間被覆蓋,必須在從這個區間上找一點,(問題變為了前者) 每次一個點可以解決一個區間或者若干個區間,這遍歷完所有區間即可
鏈接 http://nyoj.top/problem/891
代碼:
#include<stdio.h> #include<algorithm> using namespace std; const int maxn=10010; struct Node{ int beg,end; }node[maxn]; bool cmp(Node a,Node b) { return a.end<b.end; } int main() { int n,ans; while(~scanf("%d",&n)) { for(int i=0;i<n;++i)//輸入區間 並處理 { scanf("%d %d",&node[i].beg,&node[i].end); } sort(node,node+n,cmp);//將區間按右端點排序,右端點小的在前面 ans=0; int pos=-1;//pos代表第一個區間選取的點 for(int i=0;i<n;++i) { if(node[i].beg>pos) { pos=node[i].end; ++ans; } } printf("%d\n",ans); } }
-
區間完全覆蓋問題
問題描述:給定一個長度為m的區間(全部閉合),再給出n條線段的起點和終點(注意這里是閉區間),求最少使用多少條線段可以將整個區間完全覆蓋
將所有區間化作此區間的區間,剪輯一下(沒用的區間刪除)
策略:在能連接區間左邊的情況下,找到向右邊擴展最長的位置。(效果:按開頭排序,開頭一樣,右邊最長的靠前)
為了連接到這個需要被覆蓋區間的左邊,選一個左端點最靠前的區間,如果左端點相同讓右端點大的排在前面
然后向右掃描區間…,如何找下一個需要安置的區間呢,即直到找到與上一個區間沒有連接的地方,這時候必須找一個區間來來作為一個連接,因為前面區間都沒有斷開,所以在前面掃描過的區間找到一個結束處最大的區間作為連接就行,記下這個能擴展到右邊的最大位置(其實這個過程是找邊的過程)。如果這個最大位置都不能連着,證明這個區間不能被完全覆蓋!即不存在解。
代碼:
#include<stdio.h> #include<string> #include<string.h> #include<stdlib.h> #include<algorithm> #include<math.h> using namespace std; const int maxn=10010; struct Node{ double beg,end; }node[maxn]; bool cmp(Node a,Node b) { if(a.beg==b.beg) return a.end>b.end; return a.beg<b.beg; } int main() { int t,n,cnt=0; double w,h; int ans=0; double x,r; scanf("%d",&t); while(t--) { scanf("%d %lf %lf",&n,&w,&h); cnt=0; while(n--) { scanf("%lf %lf",&x,&r); if(r<=h/2.0)//過濾掉無效的噴水裝置 continue; double ll,rr;//存下該噴水裝置區間的范圍 double mid=sqrt(r*r-(h*h/4.0)); ll=x-mid; rr=x+mid;//將噴水裝置轉化為能覆蓋的區間 ll=max(0.0,ll); rr=min((double)w,rr); node[cnt].beg=ll; node[cnt].end=rr; ++cnt; } /* 此時轉化為一個區間覆蓋問題 即在一個長度為w的區間內 選出最少的區間讓這個區間覆蓋 */ node[cnt].beg=(double)w; node[cnt].end=(double)w;//加入一個終端區間[w,w]這樣遍歷到整個區間最后會找出來一個往右邊延伸到w的位置的區間,如果沒有就沒答案 ++cnt; sort(node,node+cnt,cmp); double maxpos,nowpos; nowpos=0.0; maxpos=0.0; int flag=1;// ans=0; for(int i=0;i<cnt;++i) { if(node[i].beg<=nowpos)//這個區間可以與前面的區間連着 maxpos=max(maxpos,node[i].end);//更新課擴展的最大區間 else { if(maxpos>=node[i].beg)//遇到一個間隔的 需要找一個區間補一下 { ans++; nowpos=maxpos; --i; } else//如果不能補 { flag=0; break;//無解 } } } if(flag) printf("%d\n",ans); else printf("0\n"); } }
-