这是一个非常棒的dynamic programming问题,原题的链接在这里:SPOJ Problem GCJ1C09C
题目的大致意思如下:
一个监狱里有P个并排的牢房,从左至右依次编号为1,2,...,P。最初所有的牢房里面都有一个犯人。相邻的两个牢房之间又一个窗户,可以通过它与相邻牢房里的囚犯对话。现在要释放一些囚犯。如果释放某个牢房里的囚犯,其相邻的牢房里的囚犯就会知道,因而会发生暴动。所以,释放某个牢房里的囚犯同时,必须要贿赂两旁相邻牢房里的囚犯一枚金币。另外,为了防止释放的消息在相邻牢房间传递,不仅两旁直接相邻的牢房,所有可能听到消息的囚犯,即知道空牢房或没有牢房为止,此间的所有囚犯都必须给一枚金币。现在要释放a1, a2, a3, ..., aQ号牢房的Q名囚犯,释放的顺序还没有确定。如果选择所需金币数量尽量少的顺序释放,最少需要多少枚金币?
*要求所花费的金币数量最少,那么对于释放某个囚犯而言,除了要求在释放他的时候,要求该轮释放花费金币最少,还要求在释放他之后,在释放他的左右两边囚犯也需要花费最少。仔细想想,其实释放该囚犯的这个过程,相当于释放左右区间囚犯过程的字过程,所以这个问题可以用DP的思想来解决,为了方便我们使用DP数组来记录数据,而这个问题的子问题就是:在某个确定区间内(囚犯有编号,所以可以表示成区间),如果第K个囚犯首先释放,那么这个区间花费的最小金币数是多少?
可以表达如下:
dp[i][j] = min(dp[i][k] + dp[k][j]) + index(j) - index(i) - 2, (i<k<j)
这些都确立之后我们就可以通过枚举假定最开始释放的囚犯并计算对应的金币总数。计算的对象是O(N^2)个,能够在O(N^3)以内完成
#include <iostream> #include <algorithm> #include <cstdio> using namespace std; const int maxn = 10000; const int INF = 100000; int dp[maxn + 1][maxn + 2]; //记录放出第i和j个犯人之间的所有需要放出犯人所花费的最小费用 int prisonerIndex[maxn + 2]; //记录所放出犯人的下标 void solve(int Q, int P, int time) { prisonerIndex[0] = 0; prisonerIndex[Q + 1] = P + 1; //没有犯人需要放出的区间,预置为0 for (int i = 0; i <= Q; i++) dp[i][i + 1] = 0; for (int range = 2; range <= Q + 1; range++) { for (int i = 0; i + range <= Q + 1; i++) { int j = i + range, t = INF; //假设一开始放出第k个犯人,在i,j区间中选出最小值t int k = i + 1; while (k < j) { t = min(t, dp[i][k] + dp[k][j]); k++; } //有之前的公式,prisonerIndex[j] - prisonerIndex[i] - 2就是放出k时在i,j之间花费的费用 dp[i][j] = t + prisonerIndex[j] - prisonerIndex[i] - 2; } } printf("Case #%d: %d\n", time, dp[0][Q + 1]); } int main(int argc, const char * argv[]) { // insert code here... int N, P, Q; scanf("%d", &N); for (int time = 1; time <= N; time++) { scanf("%d %d", &P, &Q); for (int i = 1; i <= Q; i++) { scanf("%d", &prisonerIndex[i]); } solve(Q, P, time); } return 0; }