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

P1282 多米諾骨牌

tdi 們的 oid clu inf div https 輸入 輸出

P1282 多米諾骨牌

題目描述

多米諾骨牌有上下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行點數之差達到最小。

技術分享

對於圖中的例子,只要將最後一個多米諾骨牌旋轉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

分析:

參照洛谷題解

這種題目方法應該都是大同小異。

考慮一個背包,

顯然如果我們直接設dp[i]表示前i個使差值最小所需的最少翻轉次數,是具有後效性的。

所以我們將直接求最值,改為求某個值是否可行,這種求最值轉變為求可行性的思想是非常實用的。

我們設dp[i][j]表示前i個數字通過某種旋轉方式使得差值為j(不帶絕對值)所需的最少翻轉次數,那麽我們最終dp結束後只需找到絕對值最小的非正無窮的dp值,輸出即可。

考慮具體的dp過程(個人采用刷表法,填表法方程類似):

dp[i+1][j+b[i]-a[i]]=min(dp[i][j]+1)//翻轉

dp[i+1][j+a[i]-b[i]]=min(dp[i][j])//不翻轉

初始化:dp[1][b[1]-a[1]]=1,dp[1][a[1]-b[1]]=0.

對於討論中出現的極端數據,一定要把初始化方程按順序(即=1的在上面,=0的在下面,這樣的話如果a[1]=b[1]那麽0就會直接覆蓋上面的1)寫就行了。

又因為Cpp語言中數組沒有負的下標,所以還要數組平移一下。P黨可以偷笑了。

至於背包不能加滾動數組的說法純屬che,dan,只是一些偽dalao的會而已。

滾動數組的方法具體可見代碼。

參考代碼:

 1 #include<cstdio>
 2 #include<algorithm>
 3 #include<cstring>
 4 #include<cmath>
 5 using namespace std;
 6 template<class T>void ChkMin(T &a,T b){if (b<a)a=b;}
 7 const int INF=0x3f3f3f3f;
 8 const int N=1010;
 9 int dp[2][20*N],a[N],b[N];//數組使勁開,我就是開小了,先是9,然後73,然後91,最後AC = =
10 int n;
11 int main(){
12     scanf("%d",&n);
13     int tot=0;
14     for (int i=1;i<=n;i++){
15         scanf("%d %d",&a[i],&b[i]);
16         tot+=abs(a[i]-b[i]);
17     }
18     memset(dp,0x3f,sizeof(dp));
19     dp[1][2*tot-a[1]+b[1]]=1;
20     dp[1][2*tot+a[1]-b[1]]=0;
21     for (int i=1;i<n;i++){
22         int cur=i&1,nxt=cur^1;
23         memset(dp[nxt],0x3f,sizeof(dp[nxt]));
24         for (int j=tot;j<=3*tot;j++)
25             if (dp[cur][j]<INF){
26                 ChkMin(dp[nxt][j+b[i+1]-a[i+1]],dp[cur][j]+1);
27                 ChkMin(dp[nxt][j+a[i+1]-b[i+1]],dp[cur][j]);
28             }
29     }
30     for (int i=2*tot;i<=3*tot;i++)
31         if (dp[n&1][i]!=INF || dp[n&1][4*tot-i]!=INF){
32             printf("%d",min(dp[n&1][i],dp[n&1][4*tot-i]));
33             break;
34         }
35     return 0;
36 }

遇見這種直接背包。

背包的本質是什麽???

是每一個物品或動作對所有當前狀態的更新。

本題就是如此,翻一次就把原來的加的差值顛倒。

並且,本題強制必須要用每一個動作,所以就必須(即+a-b或+b-a)從前面一個狀態+改變量去更新

因為改變量有正有負,所以不好降一維,

其實可以用滾動數組,但為了偷懶 (^_^),就用了二維的 (^_^)

小心負數下標數組越界!!!!!

 1 #include<cstdio>
 2 #include<iostream>
 3 #include<algorithm>
 4 #include<cstring>
 5 using namespace std;
 6 int a[1005],b[1005];
 7 int f[1005][12001];
 8 const int N=5000;
 9 int main()
10 {
11     int n,i,j,ans,dis;
12     scanf("%d",&n);
13     for(i=1;i<=n;i++)
14         scanf("%d%d",&a[i],&b[i]);
15     memset(f,0x7F,sizeof(f));
16     f[0][0+N]=0;
17 //核心代碼開始
18     for(i=1;i<=n;i++)
19         for(j=-5000;j<=5000;j++)
20         {
21             dis=a[i]-b[i];
22             f[i][j+N]=min(f[i-1][j-dis+N],f[i-1][j+dis+N]+1);
23         }
24 //核心代碼結束
25     for(i=0;i<=5000;i++)
26     {
27         ans=min(f[n][i+N],f[n][-i+N]);
28         if(ans<=1000)
29         {
30             printf("%d\n",ans);
31             break;
32         }
33     }
34     return 0;
35 }



P1282 多米諾骨牌