監獄
題目描述
有一座監獄,有N個牢房,N個牢房呈一字排成一排的。也就是說,第i個牢房緊挨着第i+1個(除了末尾那個)。每個牢房裏都關押着一名罪犯,總共N名罪犯。
上級要求將某些罪犯釋放,給了一份名單,要求每天釋放一個人。
位於相鄰牢房的罪犯,他們互相之間可以談話也可以傳話,這就使得這裏的N名罪犯都可以相互聊天。如果有一個人離開了,那麼能和說他上話的人就會很狂躁。如果想讓他們安靜下來,看守必須給狂躁的人吃一頓火鍋。但看守們希望送火鍋的次數越少越好。請你計算需要送火鍋的次數。
輸入格式
第一行兩個數N和M,M表示要釋放名單上的人數;
第二行M個數,表示釋放哪些人
輸出格式
僅一行,表示最少要給多少人次送火鍋吃。
樣例輸入
20 3
3 6 14
樣例輸出
35
樣例說明:
第1步,釋放14號罪犯,這樣左邊1到13號罪犯無法與14號聊天,他們會狂躁。右邊15到20號罪犯也無法與14號聊天,他們也會狂躁。這次共13+6=19人會狂躁;
第2步,釋放6號罪犯,這樣左邊1到5號罪犯無法與6號聊天,他們會狂躁。右邊7到13號罪犯也無法與6號聊天,他們也會狂躁。這次共5+7=12人會狂躁;
第3步,釋放3號罪犯,這樣左邊1到2號罪犯無法與3號聊天,他們會狂躁。右邊4到5號罪犯也無法與3號聊天,他們也會狂躁。這次共2+2=4人會狂躁;
最後,共19+12+4=35人次會狂躁。這是最優的方案。
數據範圍
對於 30%的數據,1≤N≤100;1≤M≤5。
對於 60%的數據,1≤N≤1000; 1≤M≤100;
對於100%的數據, 1≤N≤4000; 1≤M≤100;
這個監獄這麼好的麼…這就是傳說中最好的監獄吧
解法
讀題
對於一段連續監獄[l,r]
要釋放一個人mid,就會對[l,mid-1] [mid-1,r]的人產生影響
但是對於這段連續區間以外的人都不會產生影響
然後呢…
是不是就有點懵逼了,我怎麼知道該怎麼分啊
解題
對於這道題目,正向思維肯定是不行的,我們來逆向解題
把這道題目改成
有n個房間,要來m個犯人,除了這些犯人所在的房間沒有人之外,其它的房間都住滿了人
每來一個犯人,他都要請左邊和右邊一段連續區間的人吃火鍋
但是請這些犯人吃火鍋的錢都是這個監獄出的(畢竟是最好的監獄)
問:怎麼樣安排進入的順序才能使這些犯人的開銷最小
那麼這個時候重新分析一下
m個人可以把整個一大段區間分成m+1個小區間
我們用數組sum[i]記錄前i個區間的人數(包括犯人)之和
那麼sum[i]=第i個要進入監獄的人的位置
於是,對於某個人進入的時候他左邊的區間長度和他右邊的區間長度的值就能求出來了
由於數據很小,我們可以肆無忌憚地用動規
f[i][j]表示將第i個區間到第j個區間的人全部合併的最小代價
那麼對於f[i][j],我們就可以枚舉中間區間mid
那麼f[i][j]的求值表達是就是
f[i][j]=min(f[i][j],f[i][mid]+f[mid+1]][j]+sum[j]-sum[i-1]-2){其中i<=mid<j}
解釋一下
f[i][mid]+f[mid+1][j]表示的是之前進去的人請吃的火鍋
sum[j]-sum[i-1]-1表示的是這一整段區間的人數之和
因爲請吃火鍋的這個人本身不吃,所以還要-1
得到==>f[i][mid]+f[mid+1][j]+sum[j]-sum[i-1]-2
解完
注意
①sum[m+1]的值要賦成n+1
②f[x][x]的值都是0,但其它的值都要賦成無限大
附上對拍代碼
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
int f[123][123],ap[123];
int main()
{
freopen("prison.in","r",stdin);
freopen("prison.out","w",stdout);
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)scanf("%d",&ap[i]);
ap[m+1]=n+1;
sort(ap+1,ap+m+1);
for(int s=0;s<=m+1;s++)
{
for(int e=s;e<=m+1;e++)f[s][e]=987654321;
f[s][s]=0;
}
for(int a=1;a<=m+1;a++)
for(int s=1,e=s+a;e<=m+1;s++,e=s+a)
for(int mid=s;mid<e;mid++)
f[s][e]=min(f[s][e],f[s][mid]+f[mid+1][e]+ap[e]-ap[s-1]-2);
printf("%d",f[1][m+1]);
}