【洛谷P1622】释放囚犯【区间DP】

题目描述

Caima 王国中有一个奇怪的监狱,这个监狱一共有 P P P 个牢房,这些牢房一字排开,第 i i i 个紧挨着第 i + 1 i+1 i+1 个(最后一个除外)。现在正好牢房是满的。

上级下发了一个释放名单,要求每天释放名单上的一个人。这可把看守们吓得不轻,因为看守们知道,现在牢房中的 P P P 个人,可以相互之间传话。如果某个人离开了,那么原来和这个人能说上话的人,都会很气愤,导致他们那天会一直大吼大叫,搞得看守很头疼。如果给这些要发火的人吃上肉,他们就会安静点。

输入格式

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

输出格式

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

样例输入 #1

20 3
3 6 14

样例输出 #1

35

样例说明 #1

先释放 14 14 14 号监狱中的罪犯,要给 1 1 1 13 13 13 号监狱和 15 15 15 20 20 20 号监狱中的 19 19 19 人送肉吃;再释放 6 6 6 号监狱中的罪犯,要给 1 1 1 5 5 5 号监狱和 7 7 7 13 13 13 号监狱中的 12 12 12 人送肉吃;最后释放 3 3 3 号监狱中的罪犯,要给 1 1 1 2 2 2 号监狱和 4 4 4 5 5 5 号监狱中的 4 4 4 人送肉吃。

数据规模与约定

  • 对于 50 % 50\% 50% 的数据, 1 ≤ P ≤ 100 1 \le P \le 100 1P100 1 ≤ Q ≤ 5 1 \le Q \le 5 1Q5
  • 对于 100 % 100\% 100% 的数据, 1 ≤ P ≤ 1 0 3 1 \le P \le 10^3 1P103 1 ≤ Q ≤ 100 1 \le Q \le 100 1Q100 Q ≤ P Q \le P QP

分析

这道题看似是将区间“割裂”,但是反过来,我们用合并区间的思想做。

区间DP循环模板:长度 l e n len len+起点 i i i +分割点 k k k

f [ i ] [ j ] f[i][j] f[i][j]为合并区间 [ a [ i ] , a [ j ] ] [a[i],a[j]] [a[i],a[j]] 的最小代价。

我们把 a [ 0 ] a[0] a[0] 设为0,最后一个设为n+1。

当你选中一段连续区间中的一个人要放掉的时候,先用递归的思想考虑如何计算。对于最大的一个区间先分割一次,出现两个小区间,再分割直到有 m − 1 m-1 m1 个区间,再统计单位区间的答案。但是递归实际上是递推,所以直接从小区间的值算起,然后合并成大区间,合并的代价与分割时计算的代价是一样的。
统计答案那当然是枚举这个区间内放谁,统计这个人在这个区间内影响的人,其实就是“扩展”到整个区间有多少要吃肉的,这个用 a a a 数组减就可以了,然后要再减去自己本身和一个区间头的人

状态转移方程: f [ i ] [ j ] = m i n ( f [ i ] [ j ] , f [ i ] [ k − 1 ] + f [ k + 1 ] [ j ] + a [ j + 1 ] − a [ i − 1 ] − 2 ) ; f[i][j]=min(f[i][j],f[i][k-1]+f[k+1][j]+a[j+1]-a[i-1]-2); f[i][j]=min(f[i][j],f[i][k1]+f[k+1][j]+a[j+1]a[i1]2);

上代码

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;

int n,m,a[110],f[1010][1010],last;

int main()
{
	cin>>n>>m;
	for(int i=1;i<=m;i++) scanf("%d",&a[i]);
	a[0]=0,a[m+1]=n+1; 
	for(int len=1;len<=m;len++)
	{
		for(int i=1;i<=m-len+1;i++)
		{
			int j=i+len-1;
			f[i][j]=0x3f3f3f3f;//只能对于这个初始化,其他都是0,不然没法更新 
			for(int k=i;k<=j;k++)
			{
				f[i][j]=min(f[i][j],f[i][k-1]+f[k+1][j]+a[j+1]-a[i-1]-2);
				/*跳过k点,统计区间答案,递归思想的话就是变成子问题,但是这个地方
				是由小区间合并成大区间,-2是因为有个是本身,有个不存在*/ 
			}
		}
	}
	cout<<f[1][m];
	return 0;
} 
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值