題意:給出N組數,每一組有Si和Fi兩個數,從中選出若干組,使得Si的和TS與Fi的和TF相加最大,並且TS和TF都必須大於等於0。Si和Fi從-1000到1000。
分析:這又是一道典型的揹包問題。我們這麼想,設dp[i][j]是前i組數的TS爲j時,TF的最大值。這樣就把TS當成了體積,把TF當成了價值。只不過體積也是會不斷變化的,不確定到底體積是多少,但一定是大於等於0的。因爲Si從-1000到1000,所以TS的最小值就是-1000*100=-100000,因爲揹包問題需要參數都是正數,所以我們可以加一個100000的偏移。這樣0對應-100000,100000對應0,200000對應100000。到最後求TS+TF時只需要求j-100000+dp[j]的最大值就可以了。這樣就轉化爲01揹包的思想。
但是需要注意的是,對於01揹包來講,應該用逆向循環來保證每個物品只被取一次,但是,對於負數的情況,如果從後向前循環,就會重複利用到當前物品已經被取的結果,變成完全揹包,所以,負數的時候,應該從前往後順序循環。即若轉化爲一維數組的01揹包,爲防止重複相加,當前Si<0時應該從左往右掃,Si>0時從右往左掃。再詳細說一下:
在一般的01揹包壓縮空間的時候,體積的遍歷是從大到小,因爲dp[v]=max(dp[v],dp[v-c[i]]+w[i]),當前的dp[v]只取決於比自己小的dp[v-c[i]],所以從大到小遍歷時每次dp[v-c[i]]和dp[v]都是上一次的狀態。
如果體積爲負v-c[i]>v,從大到小遍歷dp[v-c[i]]是當前物品的狀態,不是上一個,這樣就會出錯,解決的辦法是從小到大遍歷。
見代碼:
#include <iostream>
using namespace std;
#define M 100000
#define MIN -999999
typedef struct _COW {
int s;
int f;
}COW;
COW cow[101];
int dp[2*M+1];
int max(int a, int b)
{
return a>b?a:b;
}
int main(int argc, char **argv)
{
int N;
while (cin>>N) {
for (int i=1; i<=N; ++i) {
cin>>cow[i].s>>cow[i].f;
}
for (int i=0; i<=2*M; ++i)
dp[i] = MIN;
dp[M] = 0;//初始化,前0組數中取若干組,TS爲0時,TF自然也是0.
for (int i=1; i<=N; ++i) {
if (cow[i].s<0 && cow[i].f<0)//剪枝
continue;
if (cow[i].s>0) {
for (int j=2*M; j>=cow[i].s; --j)//0-1揹包了,注意從大到小
if(dp[j-cow[i].s]>MIN)//剪枝
dp[j]=max(dp[j-cow[i].s]+cow[i].f,dp[j]);
} else {
for (int j=cow[i].s; j-cow[i].s<=2*M; ++j)//0-1揹包,從小到大
if(dp[j-cow[i].s]>MIN)//剪枝
dp[j]=max(dp[j-cow[i].s]+cow[i].f,dp[j]);
}
}
int ans=MIN;
for(int i=M;i<=2*M;i++)//剪掉了i<M的情況,即相對值i<0的情況;
{
if(dp[i]>=0)//這個一定要大於等於,因爲fi可以爲零
ans=max(ans,dp[i]+i-M);
}
cout<<ans<<endl;
}
system("pause");
return 0;
}
172MS過!done!