Luogu-P1282-多米诺骨牌

第一篇题解,纪念!


题目 P1282 多米诺骨牌

题目描述

多米诺骨牌由上下 22 个方块组成,每个方块中有 161\sim6 个点。现有排成行的上方块中点数之和记为 S1S_1,下方块中点数之和记为 S2S_2,它们的差为 S1S2\left|S_1-S_2\right|。如图,S1=6+1+1+1=9S1=6+1+1+1=9S2=1+5+3+2=11S2=1+5+3+2=11S1S2=2\left|S_1-S_2\right|=2。每个多米诺骨牌可以旋转 180°180°,使得上下两个方块互换位置。请你计算最少旋转多少次才能使多米诺骨牌上下 22 行点数之差达到最小。

示意图片

对于图中的例子,只要将最后一个多米诺骨牌旋转 180°180°,即可使上下 22 行点数之差为 00

输入格式

输入文件的第一行是一个正整数 n(1n1000)n(1\leq n\leq 1000),表示多米诺骨牌数。接下来的 nn 行表示 nn 个多米诺骨牌的点数。每行有两个用空格隔开的正整数,表示多米诺骨牌上下方块中的点数 aabb,且 1a,b61\leq a,b\leq 6

输出格式

输出文件仅一行,包含一个整数。表示求得的最小旋转次数。

思路:dp

dp表

使用一维的dp,dp[i]dp[i] 表示使上层总和为 ii 的最小翻转次数(实际上,选取上层和下层等效)。

依次输入每个牌的上层 t1t_1 和下层 t2t_2,输入的同时进行状态转移,并记录上下牌的总和 sumsum

状态

  • 初状态:所有值均为 ++\infty

  • 状态转移方程:

    1
    2
    3
    dp[i + t1] = min(dp[i + t1], dp[i]);
    dp[i + t2] = min(dp[i + t2], dp[i] + 1); // Reverse
    dp[i] = inf;
  • 末状态:为了使差最小,应该从中间 mid=sum2mid = \cfrac{sum}2 向外遍历,同时需要根据 sumsum 的奇偶讨论

    起点 循环变量范围 本次遍历下标
    mid,mid+1mid, mid+1 [0,mid][0, mid] midi,mid+1imid-i, mid+1-i
    midmid [0,mid][0, mid] midi,mid+imid-i, mid+i

AC Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
// template v6
#include <bits/stdc++.h>

using namespace std;
using ll = long long;

inline void minset(int &t, int other) {
t = min(t, other);
}

inline void maxset(int &t, int other) {
t = max(t, other);
}

const int inf = 0x7f7f7f7f;

const int N = 1010;

int sum;

int dp[N * 12]; // top numbers

int main() {
memset(dp, 0x7f, sizeof dp);
dp[0] = 0;
int n;
cin >> n;
int t1, t2;
for(int i = 0; i < n; i++) {
cin >> t1 >> t2;
sum += t1 + t2;
for(int i = sum; i >= 0; i--) {
if(dp[i] != inf) {
minset(dp[i + t1], dp[i]);
minset(dp[i + t2], dp[i] + 1);
dp[i] = inf;
}
}
}
const int mid = sum / 2;
int ans = inf;

if(sum % 2) {
// odd
for(int i = 0; i <= mid; i++) {
minset(ans, min(dp[mid - i], dp[mid + 1 + i]));
if(ans != inf){
cout << ans << endl;
break;
}
}
} else {
// even
for(int i = 0; i <= mid; i++) {
minset(ans, min(dp[mid - i], dp[mid + i]));
if(ans != inf){
cout << ans << endl;
break;
}
}
}
return 0;
}