1. 程式人生 > >洛谷 P1417烹調方案

洛谷 P1417烹調方案

這一 計算 cos 考試 什麽 UC 一道 相差 truct

題目大意:

一共有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烹調方案