P4095 [HEOI2013]Eden 的新揹包問題 & 二進位制拆分+正反01揹包
阿新 • • 發佈:2021-10-26
奇怪的題,真的很怪,調了1h
傳送門:洛谷P4095 [HEOI2013]Eden 的新揹包問題
演算法要素:二進位制拆分+正反兩次01揹包(特殊01揹包)
最開始沒什麼思路,發現可以直接多重揹包,水掉50分。
打上一個二進位制拆分,資料梯度太大,因此意義不大。
考慮離線,O(1)回答詢問,結果還掉到40了。。。
正解:
step1:二進位制拆分
step2:正反兩次01揹包,dp1[i][j]表示只選前i個,dp2[i][j]表示只選n~i個。
step3:因為原先某一物品被二進位制拆分成了多個物品,去掉該物品所在區間[l,r],合併dp1[l][j],dp2[r+1][e-j]即可得到答案
細節&問題:
(1)事實說明優化和資料結構、離線不一定會起到優化的作用,方法的選擇還是要看具體情況,分析複雜度,不能認為複雜的演算法一定會有所優化
(2)關於二進位制拆分,應為val[++cnt]=aj,而非val[++cnt]=ac
(3)關於01揹包:習慣了壓縮一維總能讓我們忘記一些關鍵的性質或者是特點,比如如果不壓縮以為就需要有dp[i][j]=dp[i-1][j]這一初始化過程。
當然壓縮一維之後顯然不需要這麼初始化以下。
(4)關於多重揹包:
列舉順序為
for(i-->物品編號)
for(遞減列舉j-->體積)
for(k-->物品數量)
很容易想到,一個物品在體積之外列舉,就會被選擇多次,同樣如果j遞增列舉,也會被選擇多次,這很顯然。
而一個物品在體積之內列舉,或者遞減列舉j,該物品就不會被選擇多次。
#include<bits/stdc++.h> using namespace std; const int maxn=1e3+40,maxm=1e5+50; int n,q,cnt; long long dp1[maxm][maxn],dp2[maxm][maxn]; long long val[maxm],w[maxm],id[maxm]; int main() { scanf("%d",&n); for(int i=0;i<=n-1;++i) { long long a,b,c; scanf("%lld%lld%lld",&a,&b,&c); for(long long j=1;j<=c;j<<=1) { val[++cnt]=a*j; w[cnt]=b*j; id[cnt]=i; c-=j; } if(c) { val[++cnt]=a*c; w[cnt]=b*c; id[cnt]=i; } } for(int i=1;i<=cnt;++i) { for(int j=0;j<=1000;++j) dp1[i][j]=dp1[i-1][j]; for(int j=1000;j>=val[i];--j) { dp1[i][j]=max(dp1[i][j],dp1[i-1][j-val[i]]+w[i]); } } for(int i=cnt;i>=1;--i) { for(int j=0;j<=1000;++j) dp2[i][j]=dp2[i+1][j]; for(int j=1000;j>=val[i];--j) { dp2[i][j]=max(dp2[i][j],dp2[i+1][j-val[i]]+w[i]); } } scanf("%d",&q); for(int g=1;g<=q;++g) { long long d,e; scanf("%lld%lld",&d,&e); int l=0,r=0; long long ans=0; while(l+1<=cnt && id[l+1]<d) ++l; r=l; while(r+1<=cnt && id[r+1]<=d) ++r; for(int j=0;j<=e;++j) { ans=max(ans,dp1[l][j]+dp2[r+1][e-j]); } printf("%lld\n",ans); } return 0; }