1. 程式人生 > >POJ - 2184 Cow Exhibition (01揹包 中 負數的處理)

POJ - 2184 Cow Exhibition (01揹包 中 負數的處理)

"Fat and docile, big and dumb, they look so stupid, they aren't much  fun..."  - Cows with Guns by Dana Lyons  The cows want to prove to the public that they are both smart and fun. In order to do this, Bessie has organized an exhibition that will be put on by the cows. She has given each of the N (1 <= N <= 100) cows a thorough interview and determined two values for each cow: the smartness Si (-1000 <= Si <= 1000) of the cow and the funness Fi (-1000 <= Fi <= 1000) of the cow.  Bessie must choose which cows she wants to bring to her exhibition. She believes that the total smartness TS of the group is the sum of the Si's and, likewise, the total funness TF of the group is the sum of the Fi's. Bessie wants to maximize the sum of TS and TF, but she also wants both of these values to be non-negative (since she must also show that the cows are well-rounded; a negative TS or TF would ruin this). Help Bessie maximize the sum of TS and TF without letting either of these values become negative. 
Input * Line 1: A single integer N, the number of cows  * Lines 2..N+1: Two space-separated integers Si and Fi, respectively the smartness and funness for each cow.  Output * Line 1: One integer: the optimal sum of TS and TF such that both TS and TF are non-negative. If no subset of the cows has non-negative TS and non- negative TF, print 0.  Sample Input
5
-5 7
8 -6
6 -3
2 1
-8 -5
Sample Output
8
Hint OUTPUT DETAILS: 

Bessie chooses cows 1, 3, and 4, giving values of TS = -5+6+2 = 3 and TF 
= 7-3+1 = 5, so 3+5 = 8. Note that adding cow 2 would improve the value 
of TS+TF to 10, but the new value of TF would be negative, so it is not 
allowed. 

題目大意 :給你一組聰明smart和funny值,求怎樣選擇可以使得smart和funny的和最大,且smart和funny都為非負;   題目分析 :這道題一開始想到用DFS來著,但想了一下發現範圍太大。又考慮了動態規劃,發現可以轉化為0-1揹包問題,但遇到了很多阻礙,一直沒有寫出來,看來一下別人的題解以後發現,思路雖然出來了,但是一些細節上的困難沒有將明白,導致我自己想了好久。為了方便大家理解和我以後可能會回來看,我就把難點好好講一下。把smart看成重量,funny作為價值。 1.第一個難點在於怎麼處理負數的情況。題目給我們的範圍是n<=100,m<=1000.所以總數和的範圍就是(-100000~100000),那我們不防將這個數整體向右平移100000個單位,這樣範圍就變成了(0~100000~200000),以100000為界分開了。但是負數帶個我們的難點還沒有解決,由於存在負數,我們無法用0或-1作為dp[]的初始值,所以我們可以用負無窮,例如-9999999(正無窮可以嗎?)這樣的數來做初始值,但是又不能全部為負無窮。我們知道初始值是我們更新得基礎,全為負無窮會導致狀態轉移的時候,無法進行正常的更新(即使更新也是一個很靠近負無窮的數)。所以我們必須將dp[100000]這個分界點標記為0.這樣我們的初步動作就做完了。

struct  Cow
{
    int sm, fu;//牛的smart和funny
}cow[maxn];

for (int i = 1; i <= n; i++)
        scanf("%d %d", &cow[i].sm, &cow[i].fu);
    for (int i = 0; i <= maxnv * 2; i++)
        dp[i] = -9999999;
    dp[maxnv] = 0;

    
   
   

2.關鍵的地方就在這裡了:還是負數導致的。我們來看狀態轉移方程:dp[j] = max(dp[j], dp[j - cow[i].sm] + cow[i].fu);紅色部分中,如果sm為負的話,-號就成了+號,逆序跟新,就會造成從前往後,而後面的是我們已經跟新過的,造成重複和錯誤跟新。所以我們要將其分開來。

for (int i = 1; i <= n; i++)
{
    if (cow[i].sm > 0)//為正逆序
    {
        for (int j = maxnv * 2; j >= cow[i].sm; j--)
            dp[j] = max(dp[j], dp[j - cow[i].sm] + cow[i].fu);
    }
    else//為負正序
    {
        for (int j = 0; j - cow[i].sm <= maxnv * 2; j++)
            dp[j] = max(dp[j], dp[j - cow[i].sm] + cow[i].fu);
    }
}

3.最後一個點在於輸出,這個我困了很久,理解起來挺有難度的,不過想一下還是會明白的。之前我們以100000為中間點,並作為跟新的基礎點。那很顯然在這個點的右邊,都是smart相加後為整數的點,那為什麼呢?我們就拿-5 7 /8 -6這兩個點解釋一下,-5是正序遍歷,也就是在100000點的左側,在靠近這個點的999995這個位置上存上7.其它位置認為負無窮。接著是8,逆序遍歷,在100008位置不會是1,因為100000位置是0,-6+0還是-6.當到了100003時,存1的值。所以只有smart相加為正時,才會出現在右側。接下來我們只要在找在右側的正數即可。你有沒有在上面的資訊中發現一個很重要的點呢?那就是j-100000就是j這個點所有smart的和,是不是很神奇。自己可以好好想想明白(相當於存位元組長度)。

for (int j = maxnv; j <= maxnv*2; j++)
    if (dp[j] > 0)
        ans = max(ans, dp[j] + j - maxnv);

AC程式碼:
#include<bits/stdc++.h>
using namespace std;
const int mid = 100000;
const int inf = 99999999;
const int  N  = 200005;
int v[105],w[105];
int dp[N];
int main()
{
    int n;
    while(scanf("%d",&n)==1){
       for(int i=1;i<=n;i++)
        scanf("%d%d",&w[i],&v[i]);
       for(int i=0;i<N;i++)  //初始化 因為有負數 所以初始化一個非常小的負數  
        dp[i]=-inf;
 
        dp[mid]=0;    //最開始的點
       for(int i=1;i<=n;i++){
        if(w[i]>0)
            for(int k=N-1;k>=w[i];k--)   //正數時候的揹包 
                dp[k]=max(dp[k],dp[k-w[i]]+v[i]);
        else
            for(int k=0;k-w[i]<N;k++)     //負數時候的揹包  
                dp[k]=max(dp[k],dp[k-w[i]]+v[i]);
       }
       int sum=0;
       for(int i=mid;i<N;i++)
        if(dp[i]>0)
            sum=max(sum,dp[i]+i-mid); //dp陣列存的是TF,通過i-mid計算TS 
        printf("%d\n",sum);
    }
}