1. 程式人生 > >POJ1015-Jury Compromise 以及 uva 323正確二維DP解法

POJ1015-Jury Compromise 以及 uva 323正確二維DP解法

這道題網上的二維DP標準演算法是完全錯誤的,花了我好幾天去思考,後來通過一點點模擬總算找到了錯誤,且聽我慢慢道來,至於錯誤程式網上有很多,可以去搜索我就不給連結了。

還有,UVA323這道題加強過資料,這才能驗證是否是正確的程式。

網上廣泛流傳的二維DP思路是已經已經選擇多少人->此次選擇哪個人->差值之和,因為不能重複,所以加入一個判斷的過程,

9 6
6 2
16 10
4 9
19 8
17 12
4 7
10 2
2 14
5 18
0 0

這組資料的答案是

Jury #1

Best jury has value 54 for prosecution and value 54 for defence:

1 2 3 4 6 9

但是錯誤程式的答案是

Jury #1
Best jury has value 52 for prosecution and value 52 for defence:
 1 3 4 5 6 8

warning:我後面講的數是指一組資料的前面一個數減去後面一個數,加起來指的是這些數加起來,而和指的是一組資料中前一個加後一個。(這樣是不是好難理解)這樣說比較方便。如這組資料:數分別為{4, 6, -5, 11, 5, -3, 8, -12, -13};加起來指的是4+6或者4+-5這種。和分別是{8,26,13,27,29,11,12,16,23}。務必要看懂這句話。

通過正確的答案和程式,執行得到dp[5][-4+6*20] = 100其中6×20是相當於零點,而-4代表的是第一組資料,9和6,下一個只要是4就可以得出正確的結論,即108。

這個資料是給錯誤的程式做準備的,因為它不是一個一個列舉,它是通過一個一個去試,然後通過上一個狀態尋找該狀態該點的最大值。

在錯誤程式測試dp[5][-4+6*20]也是得到100,這讓我趕腳很不對啊驚恐,如果是這樣的話,答案怎麼可能是52+52=104啊,明顯應該108啊。

大牛們肯定已經想到了,一定是因為那個所謂順藤摸瓜的函式,那5個數之間可能就有第一個數,所以不能選第一個數,所以答案不對,我做出假設,那5個數中間就有第一個數。那麼剩下的數之和就為-8。果然找到了。dp[4][-8 + 6*20] = 92,分別是第2,4,8,9個數的和。結果已經出來了。這個程式坑爹的bug已經完全的暴露在我們面前了大笑

就是:它無法使幾組加起來大小相同,且總的和相同的資料共存!有人可能說,這有什麼關係??關係大了,假設答案是一組資料加另外一組資料中的一個數,而程式的 那個位置上選擇的是後者。那就永遠不可能得出正確答案,這種資料挺難想出來,所以被絕大多數人忽略。

在這裡十分佩服CZDleaf在題目的discuss裡提出了疑問微笑無疑是正確的,我是一個剛開始做DP的菜鳥,想了好幾天才想明白,DP真是個死腦細胞的東西啊。

大牛們肯定一點就通,接下來我就簡單說說如何使用2維DP解這道題。灰常簡單啊!!(還是問了學長)只要把列舉N組資料放在最外面,裡面用逆推,依據揹包裡的思想。這樣就不會有重複,還有關鍵的一點,就是那個錯誤程式的bug在這個程式有木有解決,當然解決啦!得意在這裡數沒有那個重複選擇的影響,所以假設數字編號135和246是加起來一樣,和也一樣(和跟加起來是2種意思,看warning),而答案是1356,拿只筆模擬一下就會發覺根本一點關係都沒有,因為是順序的,所有組合都會遍及到不會有覆蓋情況,還是因為順序的所以連sort函式都省了。

其他具體的就看我的AC程式碼吧~

有問題歡迎大家留言討論~~

#include<cstdio>
#include<ctype.h>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<vector>
using namespace std;
int dp[21][801];
vector<int> path[21][801];

int main()
{
    int times=1;
//    freopen("input.txt","r",stdin);
//    freopen("output.txt","w",stdout);
    int subtraction[201],_plus[201];
    int n,m,i,j,k;
    while(~scanf("%d%d",&n,&m) && n && m)
    {
        for(i=0;i<m;++i)//清空vector
            for(j=0;j<801;++j)
                path[i][j].clear();
        memset(dp,-1,sizeof(dp));
        int d,p;
        for(i = 0; i < n; i++)
        {
            cin>>d>>p;
            subtraction[i] = d-p;
            _plus[i] = d+p;
        }
        int fix = 20*m;
        dp[0][fix] = 0;
        for(k = 0; k < n; k++)//選擇一個
            for(i = m-1; i >= 0; i--)//進行逆推
            {
                for(j = 0; j < 2*fix; j++)
                {
                    if(dp[i][j] >= 0)
                    {
                        if(dp[i+1][j+subtraction[k]] <= dp[i][j] + _plus[k])
                        {
                            dp[i+1][j+subtraction[k]] = dp[i][j] + _plus[k];
                            path[i+1][j+subtraction[k]] = path[i][j];//每次更新都要把path全部複製過來,就是因為這個才用的vector
                            path[i+1][j+subtraction[k]].push_back(k);
                        }
                    }
                }
            }
        for(i = 0; dp[m][fix+i] == -1 && dp[m][fix-i] == -1; i++);
        int temp = (dp[m][fix+i] > dp[m][fix-i]) ? i : -i;
        int sumD = ( dp[m][fix+temp] + temp )/2;
        int sumP = ( dp[m][fix+temp] - temp )/2;
        printf( "Jury #%d\n", times++ );
        printf( "Best jury has value %d for prosecution and value %d for defence:\n", sumD,sumP);
        for( i=0; i < m; i++ )
            printf( " %d", path[m][fix+temp][i]+1);
        printf( "\n\n" );

    }
    return 0;
}