囚犯
题目描述
阴差阳错,小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 1≤P≤1000; 1 ≤ Q ≤ 100 1≤Q≤100 1≤Q≤100; Q ≤ P Q≤P Q≤P;
且50%的数据, 1 ≤ P ≤ 100 1≤P≤100 1≤P≤100; 1 ≤ Q ≤ 5 1≤Q≤5 1≤Q≤5。
题目题解分界线
题解
原题描述的不是特别的清楚,题目大意是:现在手上有一张可释放犯人的名单,求按怎样的顺序释放犯人才能使得安慰次数最少。
对于本题,我们可以采用动态规划去解决。
仔细观察题目,我们可以得知释放犯人的顺序是由我们决定的,而且如果以已释放犯人数作为一个阶段,会使得解法有后效性。
再仔细想想,如果释放掉一个犯人,与这个犯人相邻的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]+(j−i))
设 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) (j−p[k])+(p[k]−i),化简后得 j − i j-i j−i。
但是,如果在 [ i , j ] [i,j] [i,j]这个区间里头,没有要释放的犯人,那么这个区间就不用安抚了,按照上述式子,这种情况却还要加 j − i j-i j−i次安抚次数,这个式子就错了。
我们可以在枚举 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;
}