1. 程式人生 > >區間貪心問題小結(區間選點,區間覆蓋,區間選取)

區間貪心問題小結(區間選點,區間覆蓋,區間選取)

  1. 貪心演算法

    思想:什麼是貪心演算法,什麼算得上是貪心

    貪心演算法(又稱貪婪演算法)是指,在對問題求解時,總是做出在當前看來是最好的選擇。也就是說,不從整體最優上加以考慮,只做出在某種意義上的區域性最優解。貪心演算法不是對所有問題都能得到整體最優解,關鍵是貪心策略的選擇,選擇的貪心策略必須具備無後效性,即某個狀態以前的過程不會影響以後的狀態,只與當前狀態有關。

    例題:

    1. 最少硬幣問題 有1、2、5、10、20、50、100七種面值的硬幣,要支付指定的金額,問怎麼支付所用的硬幣個數最少

      策略:緊著最大分值換。

    2. 最大斜率問題 ,給出n個點的座標(笛卡爾座標) 求(A[i]-A[j])/(i-j)最大

      策略:相鄰的座標中找到最大斜率

    3. 區間選取(會場安排問題),給一個大區間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);
          }
      }
      
      
    4. 區間選點問題,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);
          }
      }
      
      
    5. 區間完全覆蓋問題

      問題描述:給定一個長度為m的區間(全部閉合),再給出n條線段的起點和終點(注意這裡是閉區間),求最少使用多少條線段可以將整個區間完全覆蓋

      將所有區間化作此區間的區間,剪輯一下(沒用的區間刪除)

      策略:在能連線區間左邊的情況下,找到向右邊擴充套件最長的位置。(效果:按開頭排序,開頭一樣,右邊最長的靠前)

      為了連線到這個需要被覆蓋區間的左邊,選一個左端點最靠前的區間,如果左端點相同讓右端點大的排在前面

      然後向右掃描區間…,如何找下一個需要安置的區間呢,即直到找到與上一個區間沒有連線的地方,這時候必須找一個區間來來作為一個連線,因為前面區間都沒有斷開,所以在前面掃描過的區間找到一個結束處最大的區間作為連線就行,記下這個能擴充套件到右邊的最大位置(其實這個過程是找邊的過程)。如果這個最大位置都不能連著,證明這個區間不能被完全覆蓋!即不存在解。

      連結:http://nyoj.top/problem/12

      程式碼:

      #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");
          }
      }