洛谷线性DP训练(5):P1282 多米诺骨牌——变种背包

P1282 多米诺骨牌

在这里插入图片描述

输入输出样例
输入 #1复制
4
6 1
1 5
1 3
1 2
输出 #1复制
1
其他人的看法

遇见这种直接背包。

背包的本质是什么???
是每一个物品或动作对所有当前状态的更新。

本题就是如此,翻一次就把原来的加的差值颠倒。

并且,本题强制必须要用每一个动作,所以就必须(即+a-b或+b-a)从前面一个状态+改变量去更新

因为改变量有正有负,所以不好降一维,

总结目录

1.如何写出状态方程,本题与背包问题的转化
2.如何应对有负数的背包的数组,进行移位
3.如何进行初始化设置
4.最终如何搜索得到最短距离并且次数最小的值

分析

1 状态方程的书写

注意到本题中,每个牌可以翻可以不翻,翻不翻都会影响到总体,跟背包问题是非常类似的。因此考虑是否可以转化为背包问题,而且这是一个01背包问题。

定义dp[i][j]为考虑前i张牌,差和恰好为j时候的最小的翻牌次数。这里的i的意义很容易理解,但是j的定义还是有些难以理解的。这里我们定义为差和,对于1000张牌,差和的范围为-5000~5000. 本题中的资源其实有2种,一种是差和,一种是翻牌次数。i张牌最多有i次翻牌次数,但是i张牌j1次翻牌次数和j2次翻牌次数直接关系不明显,因此选择差和是更加合适的。

2 有负数的背包问题

这个问题来源于一个更加基础的问题,如何使用数组线性表示数轴上-10~10的整数?首先-10 ~ 10一共有21个数,因此我们可以考虑建立一个int num[21],并且数字i对应于下标10+i(10>=i>=-10)。

更加一般的,对于任意一个对称区间[-N,N]的整数,我们可以建立一个int num[2N+1]的数组,并且对于数字i,它的下标为N+i( N>=i>=-N)。特别的,0的下标为N,即num[N]==0;

在本题中,对应于差和可能为-5000~5000,因此建立一个dp[1005][12001](即差和存储为-6000 ~ 6000)的dp数组。N==6000. 对于j == -5000:5000,计算时使用的下标为 N+j。

由于使用恰好背包,因此计算时需要判断dp[i][j]是有意义的,由于求的是最小值,因此初始化为比较大的值,考虑到dp[i][j]的含义为次数,翻牌次数必小于牌总数n,因此不妨假设一个inf 20000,使得翻牌次数小于inf的时候判定为有效。大于inf的时候说明不存在这样的方案

for j=-5000:5000
dp[i][j]=min{ dp[i-1][j+N-cost],dp[i-1][j+N+cost] };
3 如何进行初始化设置

对于dp[0][0]的初始化理解为使用0对牌,差和为0的意义,dp[0][0]==0是合理的。其他的,dp[0][i]不存在,因此全部设为INT_MAX。

4 最终如何搜索得到最短距离并且次数最小的值

首先对于数字i,它的距离为abs(i),在数组中的下标为N+i。搜索最短距离的时候,i=n;j=-5000:5000

for j=-5000:5000
	curdist=abs(i);
	if(dp[n][j+N]<inf)//在数据有效的前提下
	if(maxdist<curdist){//距离更小直接替换
		maxdist=curdist;
		res=dp[n][j+N];
	}
	else if(maxdist==curdist){//距离相等选次数小的
		res=min(res,dp[n][j+N]);
	}

代码

#include<iostream>
#include<cstring>
#include<climits>
#include<algorithm>
#include<cmath>
#define inf 20000
using namespace std;
int a[1005], b[1005];
int dp[1005][12001];//定义dp[i][j]为前i对牌,上下差和恰好为j时候的最少需要翻牌的次数
int N = 6000;//最多有1000张匹配,每个匹配的差最大为5,因此差和最多为+-5000
int main() {
	int n;
	cin >> n;
	for (int i = 1; i <= n; i++) {
		cin >> a[i] >> b[i];
	}
	for (int i = 0; i < 1005; i++)
		for (int j = 0; j < 12001;j++) {
		dp[i][j] = INT_MAX-1;//防止溢出这里-1
	}
	dp[0][N] = 0;//恰好为[0,0]的时候置0
	for (int i = 1; i <= n; i++) {
		int cost = a[i] - b[i];//第i张匹配的cost
		for (int j = -5000; j <=5000; j++) {
			if(dp[i-1][j+N-cost]<inf||dp[i-1][j+N+cost] <inf)
			dp[i][j + N] = min(dp[i - 1][j + N - cost], dp[i - 1][j + N + cost] + 1);
		}
	}

	//最终结果是搜寻以abs(j-mid)最小为前提的dp[i][j]的最小值
	int res = INT_MAX;
	int Maxdist = INT_MAX;
	for (int i = -5000; i <= 5000; i++) {
		if (dp[n][i+N] < inf) {//首先是一个有效值
			int curdist = abs(i);
			if (Maxdist > curdist) {//距离更短就直接替换
				Maxdist = curdist;
				res = dp[n][i+N];
			}
			else if (Maxdist == curdist) {
				res = min(res, dp[n][i + N]);
			}
		}
	}
	cout << res;

	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值