1. 程式人生 > >洛谷 P1282 多米諾骨牌 (揹包dp)

洛谷 P1282 多米諾骨牌 (揹包dp)

題目描述
多米諾骨牌有上下2個方塊組成,每個方塊中有1~6個點。
上方塊中點數之和記為S1,下方塊中點數之和記為S2,它們的差為|S1-S2|。例如在圖8-1中,S1=6+1+1+1=9,S2=1+5+3+2=11,|S1-S2|=2。每個多米諾骨牌可以旋轉180°,使得上下兩個方塊互換位置。 程式設計用最少的旋轉次數使多米諾骨牌上下2行點數之差達到最小。

6 1 1 1
1 5 3 2

對於圖中的例子,只要將最後一個多米諾骨牌旋轉180°,可使上下2行點數之差為0。

輸入輸出格式

輸入格式:
輸入檔案的第一行是一個正整數n(1≤n≤1000),表示多米諾骨牌數。接下來的n行表示n個多米諾骨牌的點數。每行有兩個用空格隔開的正整數,表示多米諾骨牌上下方塊中的點數a和b,且1≤a,b≤6。

輸出格式:
輸出檔案僅一行,包含一個整數。表示求得的最小旋轉次數。

輸入輸出樣例

輸入樣例#1:
4
6 1
1 5
1 3
1 2
輸出樣例#1:
1

動態規劃不解釋(然而luogu的dalao有好多其他方法)。本蒟蒻剛開始用的是遞迴,結果TLE/AC=4/7(共11個點),36分。(為啥先用遞迴? 因為個人覺得好理解啊)
但是記憶化搜尋又不太會用,所以就轉成迴圈.

核心:狀態轉移方程 c[i][j]=min(c[i-1][j-d[i]],c[i-1][j+d[i]]+1)

i:前i個骨牌
j:前i個骨牌上下方塊點數的差
c[i][j] 表示前i個骨牌上下方塊點數差為j的最小旋轉次數(所以是min)
d[i]為第i個骨牌上下方塊點數的差

以下貌似並不是很重要的樣子:
程式設計用最少的旋轉次數使多米諾骨牌上下2行點數之差達到最小。 差達到最小 優先於 旋轉次數最小,所以從 差為0 開始向正負兩側同時尋找,第一個找到的即為最優解(當然正負側同時找到時,需要再做比較)

多米諾骨牌點數為1~6,所以單個骨牌的差最大為6-1=5;骨牌數n≤1000,所以上下之差 -5000<=j<=5000
(C++的陣列下標是負數時好像會出現什麼奇怪的東西,所以為了保險起見,做了一些改動)

下面附上程式碼:

#include<iostream>
#include<cstring>
using namespace std;
int a[1002]={0},b[1002]={0},d[1002]={0};
int c[1002][10001]={0};
int min(int a,int b)
{
    if(a>b)
        return b;
    else
        return a;
}
int main()
{
    memset(c,0x3f,sizeof(c));
    int n,i,j,k;
    cin>>n;
    for(i=1;i<=n;i++)
    {
        cin>>a[i]>>b[i];
        d[i]=a[i]-b[i];
    }
    k=1002;
    c[0][5000]=0;
    for(i=1;i<=n;i++)
        for(j=-5000;j<=5000;j++)
            c[i][5000+j]=min(c[i-1][5000+j-d[i]],c[i-1][5000+j+d[i]]+1);  //<-狀態轉移方程
    j=5000;
    while(j<10000)
    {
        if((c[n][j]<1000)&&(k>c[n][j]))
            k=c[n][j];
        if((c[n][10000-j]<1000)&&(k>c[n][10000-j]))
            k=c[n][10000-j];
        if(k<1000)
            break;
        j++;
    }
    cout<<k;
    return 0;
}

(蒟蒻第一次寫,勿噴)