NKOJ-Unknow 监狱

监狱
题目描述

    有一座监狱,有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<=midf[i][mid]+f[mid+1][j]+sum[j]-sum[i-1]-2

解完

注意

①sum[m+1]的值要赋成n+1
②f[x][x]的值都是0,但其它的值都要赋成无限大

附上对拍代码

#include 
#include 
#include 
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;mid1][e]+ap[e]-ap[s-1]-2);
    printf("%d",f[1][m+1]);
}

你可能感兴趣的:(NKOI,动态规划)