囚犯 动态规划

囚犯

题目描述

阴差阳错,小Q穿越到了明朝成为了一名狱卒头头。恰逢皇帝大寿,准备大赦天下。当然,大赦不是全赦,不然社会就全乱了。小Q所在的监狱有P个牢,这些牢房一字排开,第i个紧挨着第i+1个(最后一个除外)。相邻牢房的囚犯可以交流,现在正好牢房是满的。

现在小Q拿到了释放名单,并被要求每天释放一人。但问题是现在牢房中的P个人,可以相互之间传话。如果某个人离开了,那么原来和这个人能说上话的人,都会很气愤,那么小Q就得去一个个安抚那些气愤的人。

输入输出格式

输入格式:

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

输出格式:

仅一行,表示最少要安抚多少人次(注意每释放一个人,小Q可能都得去安抚一次,所以某个人可能会被安抚多次)。

输入输出样例

输入样例:

20 3
3 6 14

输出样例:

35

数据规模:

对于100%的数据, 1 ≤ P ≤ 1000 1≤P≤1000 1P1000 1 ≤ Q ≤ 100 1≤Q≤100 1Q100 Q ≤ P Q≤P QP
且50%的数据, 1 ≤ P ≤ 100 1≤P≤100 1P100 1 ≤ Q ≤ 5 1≤Q≤5 1Q5



题目题解分界线



题解

原题描述的不是特别的清楚,题目大意是:现在手上有一张可释放犯人的名单,求按怎样的顺序释放犯人才能使得安慰次数最少

对于本题,我们可以采用动态规划去解决。

仔细观察题目,我们可以得知释放犯人的顺序是由我们决定的,而且如果以已释放犯人数作为一个阶段,会使得解法有后效性。

再仔细想想,如果释放掉一个犯人,与这个犯人相邻的2个人就会气愤,气愤情绪会一直传递下去,直至传气愤者要传递气愤给下一个犯人时,这个犯人已经被释放;或者传递到牢房的头或尾。
释放一个犯人
我们此时可以发现:需要安抚的人形成了两个区间。

由此我们可以在本题使用区间类型动态规划

通过多次画图草稿,我们就能大概推出:

f [ i ] [ j ] = m i n ( f [ i ] [ j ] , f [ i ] [ p [ k ] − 1 ] + f [ p [ k ] + 1 ] [ j ] + ( j − i ) ) f[i][j]=min(f[i][j],f[i][p[k]-1]+f[p[k]+1][j]+(j-i)) f[i][j]=min(f[i][j],f[i][p[k]1]+f[p[k]+1][j]+(ji))

p [ k ] p[k] p[k]为释放名单上第 k k k个人,在 [ i , j ] [i,j] [i,j]这个区间里面,释放第 p [ k ] p[k] p[k]个犯人,所需的最少安抚次数就是这个犯人左边最少安抚次数 f [ i ] [ p [ k ] − 1 ] f[i][p[k]-1] f[i][p[k]1]加上右边最少安抚次数最后加上当前释放这一步需要安抚的次数 ( j − p [ k ] ) + ( p [ k ] − i ) (j-p[k])+(p[k]-i) (jp[k])+(p[k]i),化简后得 j − i j-i ji

但是,如果在 [ i , j ] [i,j] [i,j]这个区间里头,没有要释放的犯人,那么这个区间就不用安抚了,按照上述式子,这种情况却还要加 j − i j-i ji次安抚次数,这个式子就错了。

我们可以在枚举 k k k的时候顺带一个标记,如果这个区间有要释放的犯人,标记设为true,没有为false,枚举完 k k k后,标记为false,那么 f [ i ] [ j ] = 0 f[i][j]=0 f[i][j]=0,表示这个区间不用安抚。

最后输出答案 f [ 1 ] [ P ] f[1][P] f[1][P]

代码:

/*
f[i][j]=min(f[i][j],f[i][p[k]-1]+f[p[k]+1][j]+(j-i));
i : 表示区间起点
j : 表示区间终点
p[] : 可释放犯人名单
k : 名单上第k个犯人
f[i][p[k]-1] : 在i~p[k]-1这个区间的最佳释放方式
f[p[k]+1][j] : 在p[k]+1~j这个区间的最佳释放方式
释放p[k]这个犯人
需要安慰右边(j-p[k])个犯人和(p[k]-i)个犯人
总共(j-p[k])+(p[k]-i)个犯人,化简后的j-i个犯人 
*/
#include<iostream>
#define inf 99999999

using namespace std;
int n,m;
int p[105];
int f[1005][1005];
bool flag=0;

int main()
{
    
    
	cin>>n>>m;
	for(int i=1;i<=m;i++)
		cin>>p[i];
	for(int len=2;len<=n;len++)//枚举区间长度 
	{
    
    
		for(int i=1;i<=n-len+1;i++)//枚举起点 
		{
    
    
			int j=i+len-1;//区间终点 
			f[i][j]=inf;
			flag=1;
			for(int k=1;k<=m;k++)//查看此区间内是否有可释放的犯人 
				if(p[k]>=i&&p[k]<=j)
				{
    
    //如果有可释放的犯人,那么求此区间最佳释放方式 
					f[i][j]=min(f[i][j],f[i][p[k]-1]+f[p[k]+1][j]+(j-i));
					flag=0;
				}
			if(flag)
				f[i][j]=0;//如果没有,那么这个区间不需要安慰 
		}
	}
	cout<<f[1][n];
	return 0;
}

猜你喜欢

转载自blog.csdn.net/bell041030/article/details/88766986