淺談多重背包及其優化


模板來源:codevs 5429

根據背包問題的相關狀態轉移方程,我們不難寫出朴素的算法

 1 #include <iostream>
 2 #include <cstdio>
 3 #include <cstring>
 4 #include <algorithm>
 5 using namespace std;
 6 inline int read() {
 7     int ret=0;
 8     int op=1;
 9     char c=getchar();
10     while(c<'0'||c>'9') {if(c=='-') op=-1; c=getchar();}
11     while(c<='9'&&c>='0') ret=ret*10+c-'0',c=getchar();
12     return ret*op;
13 }
14 int n,m;
15 int c[7010],v[7010],num[7010];
16 int f[7010];
17 int main() {
18     n=read(); m=read();
19     for(int i=1;i<=n;i++) {
20         c[i]=read(); v[i]=read(); num[i]=read();
21     }
22     for(int i=1;i<=n;i++)
23         for(int j=m;j>=c[i];j--)
24             for(int k=0;k<=num[i];k++)
25                 if(j-c[i]*k>=0)
26                     f[j]=max(f[j],f[j-c[i]*k]+v[i]*k);
27     printf("%d\n",f[m]);
28     return 0;
29 }
TLE Code

在朴素算法中,我們枚舉每個物品的數量作為決策,這樣大大浪費時間,我們可以將物品二進制拆分來代替枚舉,具體地講,例如某種物品數量為10,那么我們將這個物品的數量拆分成1,2,4,3(相當於把這個數量為10的物品分成四個物品),然后這個問題就轉化成了01背包問題。時間復雜度大大降低。

 1 #include <iostream>
 2 #include <cstdio>
 3 #include <cstring>
 4 #include <algorithm>
 5 using namespace std;
 6 inline int read() {
 7     int ret=0;
 8     int op=1;
 9     char c=getchar();
10     while(c<'0'||c>'9') {if(c=='-') op=-1; c=getchar();}
11     while(c<='9'&&c>='0') ret=ret*10+c-'0',c=getchar();
12     return ret*op;
13 }
14 int n,m,tot;
15 int f[7010],c[100010],v[100010];
16 int main() {
17     n=read(); m=read();
18     for(int i=1;i<=n;i++) {
19         int    x=read(),y=read(),z=read();
20         int base=1;
21         while(z>=base) {
22             c[++tot]=x*base;
23             v[tot]=y*base;
24             z-=base;
25             base<<=1;
26         }
27         if(z>0) {
28             c[++tot]=z*x;
29             v[tot]=z*y;
30         }
31     }
32     for(int i=1;i<=tot;i++)
33         for(int j=m;j>=c[i];j--)
34             f[j]=max(f[j],f[j-c[i]]+v[i]);
35     printf("%d\n",f[m]);
36     return 0;
37 }
TLE Code better

 我們繼續考慮優化dp,我們將狀態j按照c[i]的余數分組,顯然,當i為定值時,不同組的狀態不能相互轉移。

我們將倒序循環j的過程,改為對每個余數u∈[0,c[i]-1],倒序循環p,這樣狀態j=u+p*c[i]。

 於是我們得到了新的狀態轉移方程:

 類比Fence一題,我們便可以使用單調隊列進行優化,這種算法的時間復雜度與普通的背包相等,可以通過本題。

 1 #include <iostream>
 2 #include <cstdio>
 3 #include <cstring>
 4 #include <algorithm>
 5 using namespace std;
 6 typedef long long ll;
 7 int n,m,c[7010],v[7010],num[7010],f[7010];
 8 int q[7010];
 9 inline int read() {
10     int ret=0;
11     int op=1;
12     char c=getchar();
13     while(c<'0'||c>'9') {if(c=='-') op=-1; c=getchar();}
14     while(c<='9'&&c>='0') ret=ret*10+c-'0',c=getchar();
15     return ret*op;
16 }
17 inline int calc(int i,int u,int k) {
18     return f[u+k*c[i]]-k*v[i];
19 }
20 int main() {
21     n=read(); m=read();
22     memset(f,0xcf,sizeof(f));
23     f[0]=0;
24     for(int i=1;i<=n;i++) {
25         c[i]=read(); v[i]=read(); num[i]=read();
26         for(int u=0;u<c[i];u++) {
27             int l=1,r=0;
28             int maxx=(m-u)/c[i];
29             for(int k=maxx-1;k>=max(0,maxx-num[i]);k--) {
30                 while(l<=r&&calc(i,u,q[r])<=calc(i,u,k)) r--;
31                 q[++r]=k;
32             }
33             for(int p=maxx;p>=0;p--) {
34                 while(l<=r&&q[l]>p-1) l++;
35                 if(l<=r) f[u+p*c[i]]=max(f[u+p*c[i]],calc(i,u,q[l])+p*v[i]);
36                 if(p-num[i]-1>=0) {
37                     while(l<=r&&calc(i,u,q[r])<=calc(i,u,p-num[i]-1)) r--;
38                     q[++r]=p-num[i]-1;
39                 }
40             }
41         }
42     }
43     int ans=0;
44     for(int i=1;i<=m;i++)
45         ans=max(ans,f[i]);
46     printf("%d\n",ans);
47     return 0;
48 }
AC Code

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM