洛谷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;
}