1. 程式人生 > >多米諾骨牌 題解

多米諾骨牌 題解

1.題目大意

給出n個多米諾骨牌,每個牌上下兩個塊都有1~6個點,上方塊中點數之和記為S1,下方塊中
點數之和記為S2,我們可以將第i張牌翻轉,讓它上下兩個塊的點數翻轉,現在
求|S1-S2|使最小的最小交換次數

2.思路思想

   首先我們可以知道s1+s2是可以確定的,且我們只用確定s1或s2就可以知道另一部分
的值,也就可以知道它們的差值。則我們就可以討論s1的值或s2的值
   我們設dp[i][j]表示用前i個牌上半部分(也就是s1)為j的最小翻拍次數(這樣就與
   01揹包差不多了)
   也就有:
   	dp[i][j]=(dp[i-1][j-a[i].up],dp[i-1][j])
   	a[i].up表示第i個牌的上邊塊的點數,如果不交換,則要構成j就是前一張牌的j-a[i].up加上a[i].up
   	如果要交換,則就是
   	dp[i][j]=min(dp[i][j],dp[i-1][j-y[i]]+1
   	因為交換所以要+1
   	**我們要求最小,就要初始化成極大值,再去取min,而邊界就是dp[0][0]=0(顯而易見)**
   	tips:因為這裡有減法,所以只有減去為非負才執行,也可以用順推

3.程式碼

有兩種方法,一種是遞推,亦可以記憶化
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
using namespace std;
int n , dp[1002][6006] , d[1003] , x[1003] , y[1003] , up_  , down_;//定義,可以用滾動
int main()
{
    scanf( "%d" , &n );
    for( int i = 1 ; i <= n ; i ++ ){
        scanf( "%d%d" , &x[i] , &y[i] );
        up_ += max(x[i] , y[i]);//可以知道它最大也大不過所有所有牌的最大點數和,把它做成
        down_ += min( y[i] , x[i] );//j變數的控制
    }
    memset( dp , 0x3f , sizeof( dp ) );
    dp[0][0] = 0;//邊界
    for( int  i = 1 ; i <= n ; i ++ ){
        for( int j = up_  ; j >= 1 ; j -- ){//01揹包是從大到小
            if(j >= x[i] )
                if( dp[i-1][j-x[i]] < 0x3f3f3f3f )
                    dp[i][j] = min( dp[i][j] , dp[i-1][j-x[i]] );
            if( j >= y[i] && dp[i-1][j-y[i]] < 0x3f3f3f3f )
                dp[i][j] = min( dp[i][j] , dp[i-1][j-y[i]] + 1 );
        }//這個就是遞推式
    }
    int minn = 0x3f3f3f3f , k=0 ;
    for( int j = 0 ; j <= up_ ; j ++ )
        if( dp[n][j] < 0x3f3f3f3f ){//來找|S1-S2|最小值,存步數
            int m = fabs( up_ + down_ - j - j );
            int m1 = fabs( up_ + down_ - minn - minn );
            if( m < m1 ){
                k = dp[n][j];
                minn = j;
            }
            else if( m == m1 && dp[n][j] < k ){
                k = dp[n][j];
                m = m1;
            }
        }
    printf( "%d" , k );//輸出
    return 0;
}