多米諾骨牌 題解
阿新 • • 發佈:2018-12-26
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; }