1. 程式人生 > 實用技巧 >洛谷P1622 釋放囚犯(dp好題)

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