<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      算法筆記(5)貪心算法

      原發(fā)表于我的博客

      貪心算法

      貪心與其說是一種算法,不如說一種思想。

      貪心思想,顧名思義,就是總是做出當前最好的選擇,這種方式可能在全局上不是最好的結果,但是在一些題目中就可以直接用。

      最簡單的例子就是“貨比三家”,在生活中,我們買東西時都會挑性價比最優(yōu)的,這就是一種貪心。

      貪心算法在OI中經(jīng)常與其他算法或數(shù)據(jù)結構結合到一起,有些題目比較考驗思維能力。

      貪心算法一般會用到的算法或數(shù)據(jù)結構不多,一般的題目只需要用到排序,有些題目可以用優(yōu)先隊列動態(tài)維護最值。

      接下來我們介紹幾種貪心算法的“套路”。

      取最優(yōu)

      最經(jīng)典的問題就是最優(yōu)裝載問題。

      商店里有\(n\)個物品,每個物品給出價值和重量,現(xiàn)在來了個小偷,小偷只能帶走\(m\)千克的物品,問他最多能帶走多少錢的物品。

      對于這道題,我們可以直接算出每一個物品的性價比,然后對于性價比排序,依次累加,直到重量超過\(m\)為止。

      代碼

      struct node{
      	int w,m;//價值和重量
      	double d;//性價比
      }a[N];
      bool cmp(node a,node b){
      	return a.d>b.d;//按照性價比從小到大排序
      }
      int n,m;
      int main(){
      	cin>>n>>m;//n個物品,限定m重量
      	for(int i=1;i<=n;i++) cin>>a[i].w>>a[i].m,a[i].d=(double)a[i].w/(double)a[i].m;
      	sort(a+1,a+1+n,cmp);//排序
      	int s1=0,s2=0;//分別為價值的累計和重量的累計
      	for(int i=1;i<=n;i++){
      		s1+=a[i].w,s2+=a[i].m;
      		if(s2+a[i+1].m>m) break;//如果超出m限制,就退出
      	}
      	cout<<s1;//輸出累計價格
      }
      

      不相交區(qū)間問題

      在一個數(shù)軸上有n條線段,現(xiàn)要選取其中k條線段使得這k條線段兩兩沒有重合部分,問最大的k為多少?

      關于這題的貪心策略,我們可以按每個區(qū)間的右端點排序,然后每個區(qū)間判斷一下是否與前面的區(qū)間重合,累加答案即可。

      代碼

      struct node{
      	int l,r;//結構體
      };
      vector<node>v; 
      int n,ans=0;
      bool cmp(node a,node b){
      	return a.r<b.r;//按右端點排序
      }
      int main(){
      	cin>>n;
      	while(n--){
      		int l,r;
      		cin>>l>>r;
      		v.push_back({l,r});//使用vector容器
      	}
      	sort(v.begin(),v.end(),cmp);
      	int l=v.front().l;//初始l為第一個區(qū)間的左端點
      	for(auto it:v){
      		if(it.l>=l){//如果不重合,就累加答案
      			ans++;
      			l=it.r;//更新l指針
      		}
      	}
      	cout<<ans;
      }
      

      區(qū)間選點問題

      給定\(N\)個區(qū)間\([a,b]\),取盡量少的點,使得每個區(qū)間內(nèi)都至少有一個點(不同區(qū)間內(nèi)含的點可以重復)。

      如何能選取更少的點呢?

      如果在幾個區(qū)間的重疊部分放點,那么就會覆蓋多個區(qū)間,就會少取一些點。

      如圖,如果兩個區(qū)間有重疊部分,那么前面區(qū)間的右端點肯定在重疊部分里。

      所以我們可以選擇在每個區(qū)間的右端點放點,這樣就會覆蓋更少的區(qū)間。

      所以我們的貪心策略就是:首先按區(qū)間的結束位置從小到大排序。然后從區(qū)間\(1\)到區(qū)間\(n\)進行選擇;對于當前區(qū)間,若集合中的數(shù)不能覆蓋它,則將區(qū)間末尾的數(shù)加入集合。

      代碼

      int vis[N];
      struct node{
      	int u,v,w;
      }a[N];
      int m,h,ans=0;
      int cmp(node a,node b){
      	return a.v<b.v;//按照右端點排序
      }
      int main(){
      	cin>>m>>h;
      	for(int i=1;i<=h;i++) cin>>a[i].u>>a[i].v>>a[i].w;
      	sort(a+1,a+1+h,cmp);//排序
      	for(int i=1;i<=h;i++){
      		int cnt=0;
      		for(int j=a[i].u;j<=a[i].v;j++) cnt+=vis[j];
      		if(cnt>=a[i].w) continue;
      		for(int j=a[i].v;j>=a[i].u,cnt<a[i].w;j--){
      			if(!vis[j]) cnt++,vis[j]++,ans++;
      		}
      	}	
      	cout<<ans;
      }
      

      中位數(shù)

      在貪心算法中,中位數(shù)是一個比較神奇的性質(zhì)。

      我們以經(jīng)典的貨倉選址問題為例。

      在一條數(shù)軸上有 \(N\) 家商店,它們的坐標分別為 \(A_1~A_N\)。現(xiàn)在需要在數(shù)軸上建立一家貨倉,每天清晨,從貨倉到每家商店都要運送一車商品。求把貨倉建在何處,可以使得貨倉到每家商店的距離之和最小。

      可以證明,當選取中位數(shù)時,距離之和最小。

      代碼

      int a[N],sum,n;
      int main(){
          cin>>n;
          for(int i=1;i<=n;i++) cin>>a[i];
          sort(a+1,a+n+1);
          int mid=a[n/2];
          for(int i=1;i<=n;i++) sum+=abs(a[i]-mid);
          cout<<sum<<endl;
      }
      

      與優(yōu)先隊列相結合

      很多貪心題目可以和堆結合到一起,在平時做題時,可以使用stl中的優(yōu)先隊列priority_queue

      合并果子是一道比較經(jīng)典的優(yōu)先隊列優(yōu)化貪心題。

      \(n\)個果子,每次可以任意選擇兩個進行合并,每次合并耗費的體力值是兩個果子重量之和,問最小耗費體力值。

      因為是任意兩個進行合并,所以我們可以每次選擇果子里面重量最小的兩個果子合并。

      但是暴力找重量最小的果子復雜度為\(O(n)\),總體\(O(n^2)\)的時間復雜度不能接受。考慮用數(shù)據(jù)結構優(yōu)化。

      什么數(shù)據(jù)結構能支持動態(tài)找最小值呢?堆!

      在這里我們可以使用stl中的優(yōu)先隊列,這樣就有了\(O(n\log n)\)的復雜度。

      代碼

      #include<bits/stdc++.h>
      using namespace std;
      priority_queue<int,vector<int>,greater<int>>q;//優(yōu)先隊列
      int n,ans=0;
      int main(){
      	cin>>n;
      	for(int i=1;i<=n;i++){
      		int t;
      		cin>>t;
      		q.push(t);//在優(yōu)先隊列中加入所有果子
      	}
      	while(!q.empty()){
      		int t1=q.top();//取出當前重量最小的果子
      		q.pop();
      		if(q.empty()) break;
      		int t2=q.top();
      		q.pop();
      		ans+=t1+t2,q.push(t1+t2);
      	}
      	cout<<ans;
      }
      

      反悔貪心

      沒人看的,所以咕了

      例題

      一本通提高篇的幾道貪心水題。

      【一本通提高篇貪心】 活動安排

      題目鏈接

      題目大意

      選取最多的區(qū)間,使它們沒有重疊部分。

      貪心思路

      按照活動的結束時間進行升序排序,因為一個活動結束的越早,就越不容易與其他活動起沖突。

      這題就是上面寫到的不相交區(qū)間問題,這里就不仔細講了,直接看代碼。

      AC代碼

      #include<bits/stdc++.h>
      using namespace std;
      const int N=1010;
      struct node{
      	int l,r;//結構體
      };
      vector<node>v; 
      int n,ans=0;
      bool cmp(node a,node b){
      	return a.r<b.r;//按右端點排序
      }
      int main(){
      	cin>>n;
      	while(n--){
      		int l,r;
      		cin>>l>>r;
      		v.push_back({l,r});//使用vector容器
      	}
      	sort(v.begin(),v.end(),cmp);
      	int l=v.front().l;//初始l為第一個區(qū)間的左端點
      	for(auto it:v){
      		if(it.l>=l){//如果不重合,就累加答案
      			ans++;
      			l=it.r;//更新l指針
      		}
      	}
      	cout<<ans;
      }
      

      【一本通提高篇貪心】 種樹

      題目鏈接

      題目大意

      即剛剛講過的區(qū)間選點問題。

      貪心思路

      按每個路段的右端點排序,枚舉每一個路段,從左端點枚舉到右端點。

      因為如果右端點重疊了,就少種了一棵樹,所以只要這個位置有樹,這個路段要求的樹的數(shù)量就-1。

      AC代碼

      #include <bits/stdc++.h>
      using namespace std;
      const int N=3e4+10;
      int vis[N];
      struct node{
      	int u,v,w;
      }a[N];
      int m,h,ans=0;
      int cmp(node a,node b){
      	return a.v<b.v;
      }
      int main(){
      	cin>>m>>h;
      	for(int i=1;i<=h;i++) cin>>a[i].u>>a[i].v>>a[i].w;
      	sort(a+1,a+1+h,cmp);
      	for(int i=1;i<=h;i++){
      		int cnt=0;
      		for(int j=a[i].u;j<=a[i].v;j++) cnt+=vis[j];
      		if(cnt>=a[i].w) continue;
      		for(int j=a[i].v;j>=a[i].u,cnt<a[i].w;j--){
      			if(!vis[j]) cnt++,vis[j]++,ans++;
      		}
      	}	
      	cout<<ans;
      }
      

      【一本通提高篇貪心】 噴水裝置

      題目鏈接

      題目大意

      \(n\) 個澆灌噴頭。每個噴頭都裝在草坪中心線上(離兩邊各\(\frac{W}{2}\)米)。我們知道每個噴頭的位置(離草坪中心線左端的距離),以及它能覆蓋到的澆灌范圍。

      請問:如果要同時澆灌整塊草坪,最少需要打開多少個噴頭?

      貪心思路

      這題怎么這么計幾?。?/s>


      如圖,每個噴水裝置覆蓋的區(qū)域不是它的直徑,我們需要用勾股定理算出那個圓和那個方塊的交集的距離,即\(\sqrt{r^2-(\frac{w}{2})^2}\)。

      算出這個距離后,就是一道裸的區(qū)間覆蓋問題了。

      所以我們的貪心思路就是,先處理處每一個區(qū)間的左右端點(即\(x-\sqrt{r^2-(\frac{w}{2})^2}\)\(x+\sqrt{r^2-(\frac{w}{2})^2}\)),然后按左端點排序,依次處理即可,細節(jié)部分看代碼。

      AC代碼

      #include <bits/stdc++.h>
      using namespace std;
      const int N=15010;
      int T,n,m;
      double l,w;
      struct node{
      	double l,r;
      }a[N];
      bool cmp(node a,node b){
      	return a.l<b.l;
      }
      void work(){
      	m=0;
      	cin>>n>>l>>w;
      	for(int i=1;i<=n;i++){
      		double id,r;
      		cin>>id>>r;
      		if(r<=w/2) continue;
      		double d=sqrt(r*r-w*w/4.0);
      		a[++m]={id-d,id+d};
      	}
      	sort(a+1,a+1+m,cmp);
      	double now=0;
      	int cnt=0,i=1;
      	while(now<l){
      		cnt++;
      		double tmp=now;
      		while(a[i].l<=tmp&&i<=m) now=max(now,a[i].r),i++;
      		if(tmp==now&&tmp<l){
      			cout<<-1<<endl;
      			return;
      		}
      	}
      	cout<<cnt<<endl;
      }
      
      int main(){
      	cin>>T;
      	while(T--) work();
      	return 0;
      }
      

      【一本通提高篇貪心】 加工生產(chǎn)調(diào)度

      題目鏈接

      題目大意

      \(n\)個物品需要加工,每個物品先在\(A\)車間加工再到\(B\)加工,問最短加工時間。

      貪心策略

      按每個物品在\(A\)\(B\)車間的加工時間的最小值排序,然后模擬即可。

      AC代碼

      #include <bits/stdc++.h>
      using namespace std;
      const int N=1010;
      int n;
      struct node{
      	int a,b,id;
      }a[N];
      int w[N];
      bool cmp2(node a,node b){
      	return min(a.a,b.b)<min(a.b,b.a);
      }
      bool cmp1(node a,node b){
      	return min(a.a,a.b)<min(b.a,b.b);
      }
      int main(){
      	cin>>n;
      	for(int i=1;i<=n;i++) cin>>a[i].a,a[i].id=i;
      	for(int i=1;i<=n;i++) cin>>a[i].b;
      	sort(a+1,a+1+n,cmp1);
      	for(int i=1,l=0,r=n+1;i<=n;i++){
      		if(a[i].a<a[i].b) w[++l]=a[i].id;
      		else w[--r]=a[i].id;
      	}
      	int s1=0,s2=0;
      	sort(a+1,a+1+n,cmp2);
      	for(int i=1;i<=n;i++){
      		s1+=a[i].a;
      		s2=max(s1,s2);
      		s2+=a[i].b;
      	}
      	cout<<s2<<endl;
      	for(int i=1;i<=n;i++) cout<<w[i]<<" ";
      }
      

      【一本通提高篇貪心】 智力大沖浪

      題目鏈接

      題目大意

      有一些任務,每個任務都有完成期限,完不成一個任務就要扣一些錢,問最多能得到多少錢。

      貪心策略

      直覺上,如果要扣錢最少,肯定得讓扣錢多的排到前面去,然后按照時間依次完成任務。

      但是這樣做是錯誤的(通過樣例就能得出結論),所以我們需要優(yōu)化一下這個思路。

      我們可以用最優(yōu)貪心,每個任務都在完成期限的最后一秒完成,用一個數(shù)組記錄下每個時間點有沒有被一個任務占用,然后向前找到最后一個能用的時間即可。

      AC代碼

      #include <bits/stdc++.h>
      using namespace std;
      const int N=510;
      struct node{
      	int t,x;
      }a[N];
      bool cmp(node a,node b){
      	return a.x>b.x;
      }
      int n,m,tim[N];
      int main(){
      	cin>>m>>n;
      	for(int i=1;i<=n;i++) cin>>a[i].t;
      	for(int i=1;i<=n;i++) cin>>a[i].x;
      	sort(a+1,a+1+n,cmp);
      	for(int i=1;i<=n;i++){
      		for(int j=a[i].t;j;j--){
      			if(!tim[j]){
      				tim[j]=1,a[i].x=0;
      				break;
      			}
      		}
      	}
      	for(int i=1;i<=n;i++) m-=a[i].x;
      	cout<<m;
      }
      

      【一本通提高篇貪心】 數(shù)列極差

      題目鏈接

      題目大意

      \(n\)個正整數(shù),每次刪去兩個數(shù)\(a,b\),放入一個數(shù)\(a\times b+1\),問最后得到的一個數(shù)的最大值和最小值。

      貪心策略

      我們會發(fā)現(xiàn)這題很像之前的例題果子合并,所以我們可以考慮用堆維護。

      而我們要同時維護最大值和最小值,可以用一個大根堆和一個小根堆。

      AC代碼

      #include <bits/stdc++.h>
      using namespace std;
      const int N=20;
      priority_queue<int,vector<int>,greater<int>>a;//大根堆
      priority_queue<int,vector<int>,less<int>>b;//小根堆
      int n;
      int main(){
      	cin>>n;
      	for(int i=1;i<=n;i++){
      		int t;
      		cin>>t;
      		a.push(t),b.push(t);//分別加入兩個中
      	}
      	//分別進行兩個堆的維護過程,具體過程很像果子合并
      	while(a.size()>1){
      		int tmp=a.top();
      		a.pop();
      		tmp*=a.top();
      		a.pop();
      		a.push(tmp+1);
      	}
      	while(b.size()>1){
      		int tmp=b.top();
      		b.pop();
      		tmp*=b.top();
      		b.pop();
      		b.push(tmp+1);
      	}
      	cout<<a.top()-b.top();//輸出題目要求維護的極差
      }
      

      【一本通提高篇貪心】 數(shù)列分段

      題目鏈接

      題目大意

      給定的一個正整數(shù)數(shù)列 ,現(xiàn)要將其分成連續(xù)的若干段,并且每段和不超過\(m\),問最少分成幾段。

      貪心策略

      這題就是最簡單的貪心了,枚舉的過程中累加和,如果超過\(m\)就情況,然后段數(shù)\(+1\),沒有什么思維含量。

      AC代碼

      #include <bits/stdc++.h>
      using namespace std;
      const int N=1e5+10;
      int t,n,m,s=0,cnt=0;
      int main(){
      	cin>>n>>m;
      	for(int i=1;i<=n;i++){
      		cin>>t;
      		if(s+t>m) s=0,cnt++;
      		s+=t;
      	}
      	cout<<cnt+1;
      }
      

      【一本通提高篇貪心】 線段

      題目鏈接

      題目大意

      在一個數(shù)軸上有\(n\)條線段,現(xiàn)要選取其中\(k\)條線段使得這\(k\)條線段兩兩沒有重合部分,問最大的\(k\)為多少?

      貪心策略

      即不相交區(qū)間問題,和習題第一道幾乎一模一樣,這里直接上代碼。

      AC代碼

      #include <bits/stdc++.h>
      using namespace std;
      const int N=1e6+10;
      struct node{
      	int l,r;
      }a[N];
      bool cmp(node a,node b){
      	return a.r<b.r;
      }
      int n,k=0;
      int main(){
      	scanf("%d",&n);
      	for(int i=1;i<=n;i++) cin>>a[i].l>>a[i].r;
      	sort(a+1,a+1+n,cmp);
      	int t=0;
      	for(int i=1;i<=n;i++){
      		if(a[i].l>=t) t=a[i].r,k++;
      	}
      	cout<<k;
      }
      

      【一本通提高篇貪心】 家庭作業(yè)

      題目鏈接

      題目大意

      有一些作業(yè),每個作業(yè)有完成期限,每個作業(yè)完成會得到一定的學分,問最多能得到多少學分。

      貪心策略

      我們會發(fā)現(xiàn)這道題和智力大沖浪那題很像,但是那題做法的時間復雜度是\(O(n^2)\),但這題的數(shù)據(jù)范圍是\(10^6\),所以需要優(yōu)化。

      一種優(yōu)化方式是用并查集優(yōu)化,并查集數(shù)組記錄下最遠能夠追溯到的放置時間,如果用路徑壓縮優(yōu)化的話,時間復雜度可以達到\(O(\alpha n)\),其中\(\alpha \le5\)

      AC代碼

      #include <bits/stdc++.h>
      using namespace std;
      const int N=1e6+10;
      struct node{
      	int t,x;
      }a[N];
      bool cmp(node a,node b){
      	return a.x>b.x;
      }
      int n,m,tim[N],fa[N],ans=0;
      int find(int x){
      	//并查集查詢
      	if(x==fa[x]) return x;//路徑壓縮
      	return fa[x]=find(fa[x]);
      }
      int main(){
      	cin>>n;
      	for(int i=1;i<=n;i++){
      		cin>>a[i].t>>a[i].x;
      		m=max(m,a[i].t);
      	}
      	for(int i=0;i<=m;i++) fa[i]=i;//初始化并查集
      	sort(a+1,a+1+n,cmp);
      	for(int i=1;i<=n;i++){
      		int d=find(a[i].t);
      		if(d) fa[d]=d-1,ans+=a[i].x;
      	}
      	cout<<ans;
      }
      
      

      【一本通提高篇貪心】 糖果傳遞

      題目鏈接

      題目大意

      \(n\)個小朋友坐成一圈,每人有\(a_i\)個糖果。每人只能給左右兩人傳遞糖果。每人每次傳遞一個糖果代價為\(1\)。求使所有人獲得均等糖果的最小代價。

      貪心策略

      本題需要一定的數(shù)學推導,比較繁瑣,這里暫時略過,讀者可以去找網(wǎng)上的題解。

      AC代碼

      #include <bits/stdc++.h>
      #define int long long
      using namespace std;
      const int N=1e6+10;
      int n,a[N],s[N],sum=0;
      signed main(){
      	cin>>n;
      	for(int i=1;i<=n;i++) cin>>a[i],sum+=a[i];
      	for(int i=2;i<=n;i++){
      		s[i]=s[i-1]+a[i]-sum/n;
      	}
      	sort(s+1,s+1+n);
      	int mid=s[n/2+1],ans=0;
      	for(int i=1;i<=n;i++) ans+=abs(s[i]-mid);
      	cout<<ans;
      }
      
      posted @ 2023-10-23 18:10  烈風光翼  閱讀(95)  評論(0)    收藏  舉報
      主站蜘蛛池模板: 国产精品亚洲а∨天堂2021| 亚洲中文久久久久久精品国产| 亚洲婷婷综合色高清在线| 国产成人女人在线观看| 宅男噜噜噜66在线观看| 亚洲更新最快无码视频| 亚洲理论在线A中文字幕| 国产精品白丝一区二区三区| 国产自在自线午夜精品| 成人无码潮喷在线观看| 肥臀浪妇太爽了快点再快点| 曰韩无码av一区二区免费| 日日噜噜夜夜狠狠久久蜜桃 | 最新国产精品中文字幕| 国产精品美女一区二三区| 福利一区二区1000| 日本一区二区三区视频版| 国产极品粉嫩尤物一区二区| 国产精品99一区二区三区| 亚洲成在人线AV品善网好看| 亚洲人成色99999在线观看| 亚洲第一国产综合| 国产国拍亚洲精品永久软件| 亚洲av午夜成人片| 鄂托克前旗| 亚洲欭美日韩颜射在线二| 蜜桃无码一区二区三区| 亚洲乱码一二三四区国产| 久久天天躁夜夜躁狠狠ds005| 亚洲大尺度无码专区尤物| 午夜综合网| 亚洲欧美在线一区中文字幕| 精品国产成人一区二区| 国内精品自线在拍| 精品久久久久久久久午夜福利| 久久久久久久久18禁秘| 国产日韩久久免费影院| 自拍视频在线观看成人| 少妇高潮激情一区二区三| 麻豆国产成人AV在线播放 | 国产综合色在线精品|