多重背包,二進位最佳化,單調隊列最佳化

來源:互聯網
上載者:User

                                                               採藥
【問題描述】
辰辰是個天資聰穎的孩子,他的夢想是成為世界上最偉大的醫師。為此,他想拜附近最有威望的醫師為師。醫師為了判斷他的資質,給他出了一個難題。醫師把他帶到一個到處都是草藥的山洞裡對他說:“孩子,這個山洞裡有一些不同的草藥,采每一株都需要一些時間,每一株也有它自身的價值。我會給你一段時間,在這段時間裡,你可以採到一些草藥。如果你是一個聰明的孩子,你應該可以讓採到的草藥的總價值最大。” 如果你是辰辰,你能完成這個任務嗎?
【輸入檔案】

輸入檔案medic.in的第一行包含兩個正整數N,M。M表示總共能夠用來採藥的時間,N代表山洞裡的草藥的數目。接下來的N行每行包括兩個的整數,分別表示採摘某株草藥的時間Ti和這株草藥的價值Vi。

【輸出檔案】
  輸出檔案為medic.out,僅包含一個整數表示規定時間內可以採到的草藥的最大總價值。
【輸入範例】
  3 9
 10 10
  8 1
 1 2
【輸出範例】
 3
【資料規模和約定】
50%的資料中 N,M ≤ 1000;
100%的資料中 N,M ≤ 100000,Ti,Vi ≤10。

若有n種物品,背包容量為m,物品體積、價值、最大使用次數為v,w,c,則樸素的動規方程為:f[i]=max{f[i-v*k]+w*k} (1<=k<=c)。我們把所有可能達到的體積按照除以當前物品體積v的餘數劃分為0~v-1,則當餘數為k(k∈[0,v-1])時又可以劃分為k,k+v,k+2*v…k+j*v…(1<=j<=(m-k)div
v)這幾種具體的體積,由於對於餘數為k時的轉移只會發生在以上列舉出的幾個體積上,所以可以建立關於以上幾個體積的單調隊列,以便於快速地找到最優決策。但是這裡要注意一點,由於這幾個決策的體積和價值都不相同,直接沒有可比性,所以我們把這些決策的體積統一到為k時比較,統一方法只需要利用體積為k+j*v這一特點,把需要插入隊中的f[k+j*v]的價值減去j*w,就是當體積為k時的一個可以用於比較的“參考”價值。可以很容易想到:由於轉移時,使用當前物品貢獻的那一部分是二者之差,所以這與減掉j*w之前是等效的。

這樣,每次求f[k+j*v]時,只需要在隊列中找到一個最優的決策f[k+j’*v],使得j-j’<=c即可,剩下的工作就只有維護單調隊列了。

核心代碼和注釋:

procedure insert(x,y:longint);//插入到一個價值單調遞減,使用次數單調遞增的隊列中
 begin
  while (l<=r)and(b[r]<=y) do dec(r);
  inc(r);a[r]:=x;b[r]:=y;
 end;

begin
 readln(n,m);  //n為物品個數、m為背包容量
 for i:=1 to n do
  begin
    read(v,w,c);  //讀入當前物品:v為物品體積、w為物品價值、c為物品可用次數

    if m div v<c then c:=m div v;  //最多可使用次數
    for k:=0 to v-1 do  //把所有的體積按除以v的餘數劃分為0~v-1
     begin
       l:=1;r:=0;  //清空隊列
       for j:=0 to (m-k) div v do  //餘數為k的又分為k,v+k,2*v+k…j*v+k…
         begin
          insert(j,f[j*v+k]-j*w);  //等效於把體積統一到k,價值減去j*w,這樣比較優劣才有意義
         while a[l]<j-c do inc(l);  //刪除次數超過c的
          f[j*v+k]:=b[l]+j*w;      //用隊列頭的值更新f[j*v+k]
         end;
     end;
  end;
  writeln(f[m]);

end.

/*多重背包問題:第一種為二進位最佳化 時間複雜度 O(NMlog2S)             第二種使用單調隊列 時間複雜度 O(NM) */ //方法一 #include <cstdio>#define max(a,b) (a)>(b)?(a):(b)int dp[100001];int w[11][11];int main(){  freopen("medic.in","r",stdin);  freopen("medic.out","w",stdout);  int n,m;  scanf("%d%d",&n,&m);  int a,b;  for(int i = 0; i < n; ++i)  {    scanf("%d%d",&a,&b);    w[a][b]++;  }  for(int i = 1; i < 11; ++i)//多重背包   {    for(int j = 1; j < 11; ++j)    {      if(!w[i][j]) continue;            if(w[i][j]*i < m)//01背包       {        int k = 1;        int count = w[i][j];        while(k < count)//2進位思想,每次處理一堆        {         for(int v = m; v >= k*i; --v)              dp[v] = max(dp[v],dp[v-k*i]+k*j);                        count -= k;         k *= 2;        }        for(int v = m; v >= count*i; --v)            dp[v] = max(dp[v],dp[v-count*i]+count*j);      }     else//完全背包      {       for(int v = 1; v <= m; ++v)       if(v-i>=0) dp[v] = max(dp[v],dp[v-i]+j);     }    }  }  int ans = 0;  for(int i = 0; i <= m; ++i)   if( ans < dp[i]) ans = dp[i];     printf("%d\n",ans);    return 0;}/* 方法2 #include <cstdio>#define max(a,b) (a)>(b)?(a):(b)int f[100001];int ww[11][11];int l,r;int a[100001];int b[100001];inline void insert(int x,int y){  while(l<=r && b[r]<=y) r--;  ++r;  a[r] = x;  b[r] = y;}int main(){  freopen("medic.in","r",stdin);  freopen("medic.out","w",stdout);  int N,m;  scanf("%d%d",&N,&m);//m為背包容量   int cc,dd;  int sum = 0;  for(int i = 0; i < N; ++i)  {    scanf("%d%d",&cc,&dd);    ww[cc][dd]++;  }  for(int i = 1; i < 11; ++i)   for(int jj = 1; jj < 11; ++jj)    {      if(!ww[i][jj]) continue;//處理每件物品       int c = i;//物品的耗費       int w = jj; //物品價值       int n = ww[i][jj];//物品個數       if(m/c < n) n = m/c; //最多可使用次數      for(int k = 0; k <= c-1; ++k)//把所有的體積按除以v的餘數劃分為0~v-1      {        l = 1;r = 0; //清空隊列        for(int j = 0; j <= (m-k)/c; ++j)//餘數為k的又分為k,v+k,2*v+k…j*v+k…        {          //insert(j,f[j*c+k]-j*w);//等效於把體積統一到k,價值減去j*w,這樣比較優劣才有意義          int x = j;          int y = f[j*c+k]-j*w;          while(l<=r && b[r]<=y) r--;          ++r;          a[r] = x;          b[r] = y;          while(a[l]<(j-n)) ++l; //刪除次數超過n的          f[j*c+k] = b[l]+j*w; //用隊列頭的值更新f[j*c+k]        }      }    }  printf("%d\n",f[m]);  return 0;}*/ 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.