洛谷 P1417烹調方案
題目大意:
一共有n件食材,每件食材有三個屬性,ai,bi和ci,如果在t時刻完成第i樣食材則得到ai-t*bi的美味指數,用第i件食材做飯要花去ci的時間。
求最大美味指數之和。
分析:
顯然的0/1背包,但是,它與平常的0/1背包不同之處在於:平常的物品不會因為時間的延續而使價值貶值,也就是說,先放a、先放b是無所謂的。
但是這個題,“ai-t*bi”的判斷方法,顯然相同物品的不同放置順序,都可能得到不同的答案。
所以必然要排序。
但是怎麽排序?
按照ci排?但是不一定時間短的要先做,可能其它食物b太大,貶值的很厲害。
按照bi排?但是先做貶值快的,可能由於做的時間長,仍然可能造成其它剩余食物貶值總和更大。
按照(sumb-bi)*ci排?但是由於不一定做i的時候,其它的所有的食物都留下等著做,損失其實不一定有sumb-bi那麽大
按照ai-(sumb-bi)*ci排?但是這其實是只考慮了第一個做誰,仍然可能不是最優子結構。
我們這樣考慮:
設身處地地想一想,我們假設已經過了p時間,還剩下兩個食材x,y,你會怎麽辦?
一定會考慮,先做x或者先做y哪個會最大收益。
先做x:
a[x]-(p+c[x])*b[x]+a[y]-(p+c[x]+c[y])*b[y] —— ①
先做y:
a[y]-(p+c[y])*b[y]+a[x]-(p+c[y]+c[x])*b[x] —— ②
對這兩個式子化簡,得到①>②的條件是c[x]*b[y]<c[y]*b[x].
所以,對於有若幹個食物,道理同樣如此。
我們先按這個標準排一下序,然後0/1背包即可。
提醒:如果不用滾動數組的話,雖然好理解,但是要記得,不論j>=food[i].c與否,必須要有一個f[i][j]=f[i-1][j]
這裏,我定義f[i][j]為,前i個物品,最後一次做完飯是在j時刻。也可以省略第一維。
(至於為什麽要排序,假設現在x優於y,如果我們先循環的是x,就代表會先做x,再做y的時候,可以從上一次做完x的時刻轉移過來美味程度。
如果y在了前面循環,那麽想要同時做x,y,x必須從上一次的某一個y處轉移過來,那麽這個時候,x一定不是第一個做的。
或者,就算是x想要第一個做,但是y已經循環過去了,不會再從x做完後的時間轉移到更靠後的j,也就扔掉了正解的轉移路徑。)
代碼:
#include<bits/stdc++.h> #define ll long long using namespace std; const int N=55; const int T=100000+10; ll f[T]; int sb; int n,t; struct node{ ll a,b,c; bool friend operator <(node a,node b) { return a.c*b.b<b.c*a.b; } }foo[N]; int main() { cin>>t>>n; for(int i=1;i<=n;i++) scanf("%lld",&foo[i].a); for(int i=1;i<=n;i++) scanf("%lld",&foo[i].b); for(int i=1;i<=n;i++) scanf("%lld",&foo[i].c); sort(foo+1,foo+n+1); for(int i=1;i<=n;i++) for(int j=t;j>=foo[i].c;j--) { f[j]=max(f[j],f[j-foo[i].c]+foo[i].a-foo[i].b*j); } ll ans=0; for(int i=1;i<=t;i++) ans=max(ans,f[i]); cout<<ans; return 0; }
同理的,還有一道考試題:
再過T天就是Kate的生日了,Coffee打算送它一些蛋糕。在Coffee和Kate的世界,蛋糕是做不出來的,而是種出來了。
Coffee收藏著N個蛋糕種子。第i個蛋糕種子需要在室內培育連續Pi天才能長成幼苗,之後將它移栽到室外,再經過Qi天後就會結出一個美味度為Ri的蛋糕。移栽蛋糕苗、為室外的蛋糕澆水以及收取成熟的蛋糕花費的時間對於Coffee來說可以忽略不計,但由於長成幼苗前的蛋糕比較嬌嫩,照顧起來也更麻煩,Coffee每天最多只會照顧一棵蛋糕幼苗。
更具體地,如果Coffee在第t0天開始室內培育第i個蛋糕種子,那麽它的室內培育工作會占用Coffee第t0..t0+(Pi)-1,並會在t0+Pi+(Qi)-1天結出蛋糕。在t0..t0+(Pi)-1天,Coffee不會開始或同時培養其他種子。
Coffee希望在Kate生日時,送給Kate的蛋糕們的美味度總和盡量大。也就是說,在接下來的T天內,Kate最多能收獲蛋糕的美味度總和最大是多少?註意,即使一個蛋糕苗在第T天前已經被移出了室外,只要它在Kate的生日前沒能結出蛋糕,它的美味度就不會被計算到總美味度中。
分析:
發現Q這個延時很麻煩,處理的時候還得想之前有沒有種過。可以取巧地,假設過完了P天,就立刻熟了,收獲了,但是“收獲”的這一天不能晚於T-Qi(這個轉化就是合法的)
所以就剩一個0/1背包了。
因為對於不同的物品,有不同的背包上界,既然如此,肯定先考慮,把上界低的先放進包裏,因為再高了就放不了了。
上界高的,能力大,就可以放到再靠上的包的空間裏。
也就是把公共的區間讓範圍小的先去填充。排序即可。
#include<cstdio> #include<cstdlib> #include<algorithm> #include<iostream> using namespace std; const int N=300+10; const int T=20000+10; int f[N][T]; struct node{ int cost,dis,val; }ca[N]; int n,t; bool cmp(node a,node b) { if(a.dis!=b.dis) return a.dis>b.dis; return a.cost<b.cost; } int main() { scanf("%d%d",&n,&t); for(int i=1;i<=n;i++) scanf("%d%d%d",&ca[i].cost,&ca[i].dis,&ca[i].val); sort(ca+1,ca+n+1,cmp); for(int i=1;i<=n;i++) { for(int j=t-ca[i].dis;j>=1;j--) {f[i][j]=f[i-1][j]; if(j>=ca[i].cost) f[i][j]=max(f[i][j],f[i-1][j-ca[i].cost]+ca[i].val);} for(int j=1;j<=t;j++) cout<<f[i][j]<<endl; cout<<endl; } int ans=0; for(int i=1;i<=t;i++) ans=max(ans,f[n][i]); printf("%d",ans); return 0; }
總結:
1.背包的變式其實是非常多的,總體的定義、循環相差不大,但是關鍵點多出在排序。
2.排序本質上都是兩兩之間的物品進行最優解的比較,所以考慮如何排序的時候,可以嘗試單獨考慮這兩個元素之間的大小關系,從而列出式子。
3.背包不能憑感覺瞎想,一定要分析好物品循環的先後順序。
4.背包本質上還是背包,就是往有限空間裏放東西,只是東西千奇百怪罷了,但是考慮方式是可以轉化到基礎的放東西模型上的。
洛谷 P1417烹調方案