【01背包问题变体】P1282 多米诺骨牌
题目
P1282 多米诺骨牌
分析
题目分析
目标:通过旋转多米诺骨牌,使上下两行点数之差的绝对值最小,在最小差值的前提下旋转次数最少。
关键信息:
- 每个骨牌可以旋转180°(上下互换)
- 旋转次数要最少
- 最终差值要最小
思考过程
第一步:识别问题类型
这是一个双目标优化问题:
- 主要目标:最小化差值绝对值
- 次要目标:在最小差值下最小化旋转次数
第二步:分析决策过程
对于每个多米诺骨牌,我们有两个选择:
- 不旋转:保持原来的上下关系
- 旋转:上下互换
第三步:思考状态表示
尝试1:简单的线性状态
f[i] = 前i个骨牌的最小旋转次数
问题:无法表示当前的差值状态,不知道旋转后差值是多少。
尝试2:二维状态
f[i][j] = 前i个骨牌,旋转j次的最小差值
问题:这样设计无法同时优化两个目标。
尝试3:正确的二维状态
f[i][j] = 前i个骨牌,当前差值为j时的最小旋转次数
为什么这样设计?
i
:处理到第几个骨牌j
:当前的差值(可以是负数)- 值:达到这个差值需要的最小旋转次数
第四步:分析状态转移
对于第i个骨牌,设其上下点数分别为a[i]
和b[i]
:
情况1:不旋转
- 差值贡献:
a[i] - b[i]
- 如果前i-1个骨牌的差值是
j - (a[i] - b[i])
,那么加上第i个骨牌后差值变成j
- 转移:
f[i-1][j - (a[i] - b[i])] → f[i][j]
情况2:旋转
- 差值贡献:
b[i] - a[i] = -(a[i] - b[i])
- 如果前i-1个骨牌的差值是
j - (b[i] - a[i])
,那么加上第i个骨牌后差值变成j
- 转移:
f[i-1][j - (b[i] - a[i])] + 1 → f[i][j]
简化表示
设diff[i] = a[i] - b[i]
:
- 不旋转:
f[i-1][j - diff[i]] → f[i][j]
- 旋转:
f[i-1][j + diff[i]] + 1 → f[i][j]
第五步:边界条件
f[0][0] = 0 // 0个骨牌,差值为0,旋转次数为0
f[0][j] = INF (j ≠ 0) // 0个骨牌不可能有非零差值
第六步:答案提取
由于我们要最小化差值的绝对值,应该从差值0开始搜索:
for(int j = 0; j <= max_diff; j++) {if(f[n][j] != INF || f[n][-j] != INF) {return min(f[n][j], f[n][-j]);}
}
关键思考
1. 为什么用差值作为状态维度?
- 差值直接反映了我们的优化目标
- 通过记录所有可能的差值,我们可以在最后选择最小的
2. 为什么用旋转次数作为状态值?
- 旋转次数是我们的次要优化目标
- 在相同差值下,我们选择旋转次数最少的方案
3. 为什么这样设计能同时优化两个目标?
- 通过遍历所有可能的差值,我们找到了最小可达差值
- 对于每个差值,我们记录了达到该差值的最小旋转次数
- 因此自然实现了"最小差值下的最小旋转次数"
设计思路总结
- 识别双目标:最小化差值 + 最小化旋转次数
- 确定主次:差值为主,旋转次数为次
- 状态设计:用差值作为状态维度,旋转次数作为状态值
- 转移分析:考虑每个骨牌的两种选择
- 答案提取:从最小差值开始搜索
这种设计思路的核心是:将次要目标作为状态值,将主要目标作为状态维度,这样可以在DP过程中同时优化两个目标。
代码
#include<iostream>
#include<cstring>using namespace std;const int N = 1010, M = 1e4 + 10, INF = 0x3f3f3f3f;int n, a[N], f[N][M];int m = 5000;int main()
{cin >> n;for(int i=1;i<=n;i++){int x,y; cin >> x >> y;a[i] = x - y;}memset(f,0x3f,sizeof f);f[0][0+m] = 0;for(int i=1;i<=n;i++){for(int j=-m;j<=m;j++){f[i][j+m] = min(f[i-1][j-a[i]+m],f[i-1][j+a[i]+m]+1);}}int ret = INF;for(int j=0;j<=m;j++){ret = min(f[n][j+m],f[n][-j+m]);if(ret != INF) break;}cout << ret;return 0;
}