【传送门】
题目描述
多米诺骨牌有上下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。
输出格式
输出文件仅一行,包含一个整数。表示求得的最小旋转次数。
输入输出样例
思路
对于有多个物体,每个物体只有两个状态的,一般都是01背包了
定义状态
dp[i][j] 表示0-i
下标号骨牌能够形成上下差值为j
的局面,需要的最少移动次数
注意:
因为这里骨牌的差值可能是负的,所以用数组存的话,加一个偏移量就行,最大值不超过6000,那么我使用6009作为偏移,即0下标表示差值为-6009,6009下标表示差值为0
状态转移:
设上面的数字用n1[]
数组存储,下面的用n2[]
数组
对于第i
块骨牌,如果当前局面的差值是j
- 如果骨牌正着放,那么
0~i-1
下标会形成差值为j-n1[i]+n2[i]
的局面 - 如果骨牌倒着放,那么
0~i-1
下标会形成差值为j-n2[i]+n1[i]
的局面,问题答案为上述局面答案+1 - 这一块骨牌是否倒放,取决于从上述两个局面,形成差值为
j
的局面,哪一个需要反转的次数少
dp[i][j] = min(dp[i-1][j-n1[i]-n2[i]], dp[i-1][j-n2[i]-n1[i]]+1)
因为枚举的是差值(价值),计算完dp数组之后,那么从0往两边找差值最小的,且移动次数最小的即可
代码
#include <bits/stdc++.h>
using namespace std;
int n1[1009], n2[1009];
int n;
int dp[1009][6009*2];
int main()
{
cin>>n;
for(int i=0; i<n; i++) cin>>n1[i]>>n2[i];
for(int i=0; i<1009; i++)
for(int j=0; j<6009*2; j++) dp[i][j]=1145141919;
for(int i=0; i<6009*2; i++)
if(n1[0]-n2[0]+6009==i) dp[0][i]=0;
else if(n2[0]-n1[0]+6009==i) dp[0][i]=1;
for(int i=1; i<n; i++)
for(int j=0; j<6009*2; j++)
if(j-n1[i]+n2[i]>=0 && j-n2[i]+n1[i]>=0)
dp[i][j] = min(dp[i-1][j-n2[i]+n1[i]]+1, dp[i-1][j-n1[i]+n2[i]]);
int i=6009, j=6009;
while(i>=0 && j<6009*2)
{
if(dp[n-1][i]<=n || dp[n-1][j]<=n)
{
cout<<min(dp[n-1][i], dp[n-1][j])<<endl;
break;
}
i--; j++;
}
return 0;
}