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