题意:
有P个相邻的牢房(序号1,2,…,P),每个牢房中关押着犯人一名
现在要释放Q个牢房中的犯人(序号给出),释放后牢房为空
每次释放一名时需要依次向左向右给其他牢房犯人每人一枚金币直到遇到空牢房为止
可以按照任何顺序释放犯人,输出所需最小金币数。
思路:
为方便理解,预处理出序号为0和序号为P+1的空牢房。
可以想到,如若释放了序号i(0<i<P+1)号牢房的犯人,则对0到i牢房组和 i到P+1牢房组的讨论完全独立且其最优解可构成0到P+1的最优解(算导将其称作最优子结构)
故可以用动态规划的思想:
定义状态dp[i][j] (0<=i<=j<=P+1) 为释放从i到j之间(不包括i,j)的所有需释放的犯人所需最小金币数(i与j之间无空牢房)
则可得转移方程为
考虑递归树的底层,转移顺序应该从短区间开始转移(枚举区间长度即可)
d[i][j] = (j-i-1)+d[i][v]+d[v][j] (i<v<j)
d[0][P+1]即为所求最优解。
P.S. 此处的未释放但需释放的犯人可以理解成隐式的空牢房。
#include <stdio.h>
#include <string.h>
#include <algorithm>
#define LL long long
#define MM 10010
#define MAX 1e16
LL dp[MM][MM];
int free_num[MM];
using namespace std;
int main()
{
int P, Q;
while(scanf("%d %d", &P, &Q)==2)
{
memset(dp, 0, sizeof(dp));
for (int i = 1; i <= Q; ++i)
scanf("%d", &free_num[i]);
free_num[0] = 0;
free_num[Q+1] = P+1;
for (int diff = 2; diff <= Q+1; ++diff)
for (int head = 0; head+diff <= Q+1; ++head)
{
LL temp = MAX;
int tail = head + diff;
for (int v = head+1; v < tail; ++v)
temp = min(temp, dp[head][v] + dp[v][tail]);
dp[head][tail] = temp + free_num[tail] - free_num[head] - 2;
}
printf("%d\n", dp[0][Q+1]);
}
}