洛谷P1622 释放囚犯(dp好题)

释放囚犯

题目描述

(Caima) 王国中有一个奇怪的监狱,这个监狱一共有(P)个牢房,这些牢房一字排开,第(i)个紧挨着第(i+1)个(最后一个除外)。现在正好牢房是满的。
上级下发了一个释放名单,要求每天释放名单上的一个人。这可把看守们吓得不轻,因为看守们知道,现在牢房中的(P)个人,可以相互之间传话。如果某个人离开了,那么原来和这个人能说上话的人,都会很气愤,导致他们那天会一直大吼大叫,搞得看守很头疼。如果给这些要发火的人吃上肉,他们就会安静点。

输入格式和输出格式

输入格式

第一行两个数(P)(Q)(Q)表示释放名单上的人数;
第二行(Q)个数,表示要释放哪些人。

数据规模
对于(100%)的数据(1≤P≤1000)(1≤Q≤100)(Q≤P);且(50%)的数据 (1≤P≤100)(1≤Q≤5)

输出格式

仅一行,表示最少要给多少人次送肉吃。

输入和输出样例

输入#1

20 3
3 6 14

输出#1

35

本题思路

这道题看起来和一般的区间dp有很多出入,不是从小到大,而是从大到小,看起来要是分成不同的子问题,每个子问题还是会互相干扰的,不妨模拟一遍过程可以发现,好像和石子合并(不要问我什么是石子合并)的过程刚好相反,说白了就是石子合并的逆过程,如果一个大区间中挑选一个切点,那么就分成了两个互不相干的子问题(因为只与切点有关,先选择了切点之后分成的两个区间就毫无关联),再想,把分成的两个小区间再次选切点,就又分成了两个互不相干的两个更小的子问题,依次类推,可以发现这和石子合并没有任何区别,因此,这道题的大致思路没问题了,我们可以推出转移方程:(f[i][j]=min{f[i][j],f[i][k-1]+f[k+1][j]+a[j+1]-a[i-1]-1-1}), 把后面这一坨拿出来拆开看看,(f[i][k-1]+f[k+1][j]+a[j+1]-a[i-1]-1-1), (f[i][k-1]+f[k+1][j]),这个不必解释,(a[j+1]-a[i-1]-1)就是第(j+1)个要放出的囚犯到第(i-1)个要放出的囚犯之间的人数,也就是要发的肉的数量;
最后一个(-1) 是什么呢,就是第k个放出去的囚犯,不用给他吃肉了。

代码实现

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e3+50;
int P, Q, a[maxn], dp[maxn][maxn];
inline int read(){
	char ch = getchar();
	int f = 1, x = 0;
	while(!isdigit(ch)){if(ch == '-') f = -1;ch = getchar();}
	while(isdigit(ch)){x = x*10;x += ch - '0';ch=getchar();}
	return x*f;
}
int main(){
	P = read(),Q = read();
	for(int i = 1; i <= Q; i++){
		a[i] = read();
	}
	sort(a+1, a+Q+1);
	a[0] = 0;
	a[Q+1] = P+1;
	for(int len = 1; len <= Q; len++){
		for(int l = 1; l + len - 1 <= Q; l++){
			int r = l + len - 1;
			dp[l][r] = 0x3f3f3f3f;
			for(int j = l; j <= r; j++){
				dp[l][r] = min(dp[l][r], dp[l][j-1] + dp[j+1][r] + a[r+1] - a[l-1] - 2);
			}
		}
	}
	printf("%d",dp[1][Q]);
	return 0;
}
原文地址:https://www.cnblogs.com/hzoi-liujiahui/p/13270375.html