题解 P1622 释放囚犯

题目链接

题面

题目描述

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

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

输入格式

第一行两个整数 $P$ 和 $Q$,$Q$ 表示释放名单上的人数;

第二行 $Q$ 个整数,表示要释放哪些人,保证按递增的顺序给出。

输出格式

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

样例 #1

样例输入 #1

1
2
20 3
3 6 14

样例输出 #1

1
35

提示

样例说明 #1

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

数据规模与约定

  • 对于 $50\%$ 的数据,$1 \le P \le 100$,$1 \le Q \le 5$;
  • 对于 $100\%$ 的数据,$1 \le P \le 10^3$,$1 \le Q \le 100$,$Q \le P$,保证释放的人所在的牢房编号按递增的顺序给出。

思路

题意即求释放列表中所有罪犯的费用之和最小,每次释放,罪犯i到他两边空牢房之间的所有人,都需要消耗一块肉。每次释放一个罪犯,可以想到区间 DP,状态 f[l][r] 表示从lr号罪犯全部释放所需要消耗的肉块总数。

之后思考状态转移方程。要求最小值,f[l][r] 初始值 INT_MAXf[l][r] = min of f[l][m-1] + f[m+1][r] + Pos[r+1] - Pos[l-1] -2 ,其中 m 属于区间 [l,r],枚举合并点,即此次释放的罪犯,罪犯两侧到lr号罪犯的消耗f[l][m-1]f[m+1][r]Pos[i]定义i号罪犯的编号。这里其实是一种反向的思路,m点应该是最先释放的(?Pos[r+1] - Pos[l-1] -2即此区间两侧最近的罪犯之间的人数,减去区间两端。即区间合并的额外费用。注意要在Pos数组中定义Pos[Q+1]=N+1

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <cstdio>
#include <cstring>
#include <iostream>
#include <climits> // For INT_MAX

using namespace std;

int Pos[110];
int f[110][110];

int main() {
int P, Q;
cin >> P >> Q;
for(int i = 1; i<=Q; i++){
cin >> Pos[i];
}
Pos[Q+1] = P+1;
for(int k = 1; k<=Q; k++){
for(int i = 1; i+k-1<=Q; i++){
int y = i+k-1;
f[i][y]=INT_MAX; // 初始值
for(int m = i; m<=y; m++){
f[i][y] = min(f[i][m-1] + f[m+1][y], f[i][y]);
}
f[i][y] += Pos[y+1] - Pos[i-1] -2;
}
}
cout << f[1][Q] << endl; // 答案即把 1...Q 所有的罪犯全部释放(合并)
return 0;
}

小结

本题难点在于建模,区间DP部分比较典型。