在过去的11个小时43分钟里,这个男人尝试入门区间DP终于告一段落
问题描述
炫炫回了内蒙,肯定要吃羊肉啦,所有他家要宰羊吃。
炫炫家有N只羊,羊圈排成一排,标号1~N。炫炫每天吃掉一只羊(这食量!其实是放生啦),吃掉的羊的邻居会以为它被放生了,然后又会告诉他们的邻居,这样一直传播下去,除非某个邻居已经被“放生”了。每一天,所有知道某羊被“放生”了这个消息的羊都会很不满,如果不给他们巧克力的话,他们就会很造反,炫炫已经知道他要吃掉哪些羊,他可以任意安排吃的顺序,然后使巧克力的用量最小,请求出这个最小值。
输入格式
本题有多组数据,第一行为数据组数T。
对于每组数据
第一行:两个用空格隔开的整数:N和M,表示羊的数量和需要吃掉的数量
第二行:有M个数,表示要吃那些羊。
输出格式
T行,为每组数据的答案。
样例输入
2
8 1
3
20 3
3 6 14
样例输出
7
35
数据规模和约定
T=10
N<=10000
M<=100
首先观察所有要宰的羊a[1]、a[2]、a[3]、a[4]…a[m]
从题目里我们可以了解到,当a[k]被宰时,a[k]所在的位置即成为其他羊交流无法跨过的障碍,也就是ak的左侧与a[k]的右侧处于完全独立状态。
那么如果我们有一种方法能够算出a[1]一直到a[k-1],这k-1只羊全部被宰的最小代价与a[k+1]一直到a[m]这m-k只羊全部被杀的最小代价,以及杀掉a[k]的最小代价,三者相加就是杀掉所有要杀的羊的最小代价了。
从上面我们了解到,每当计算left到right这段区间我们都要用到它的子区间left到k-1与k+1到right,那计算形式可以理解为先计算长度小的区间再计算长度大的区间。我们可以通过二次循环来让len从小到大,对于每个len再让左端点从小到大
如果用f[left][right]来表示杀掉a[left]一直到a[right]的最小代价,再枚举k
思路可以表示成下图👇
可以看到思路里没有给出a[k]代价的计算方法,接下来我们详细讨论这点。
a[k]代价的计算
一句话概括:杀left到right这些羊,是因为从len更长的递归了过来所以left和right两侧所要杀的羊都已经被杀掉了即a[right+1]与a[left-1]已经被杀,所以杀a[k]的代价是a[right+1]-a[left-1]-2
我们画个图来理解一下
红颜色笔画的数字是要杀的🐏,蓝颜色笔画的是不要杀的🐏
那么a[1]=2,a[2]=4,a[3]=6,a[4]=7,a[5]=8,a[6]=10
我们考虑left=2,right=5即杀死待杀编号2到5的所有待杀羊的最小代价(就是图中红色波浪线画出来的一块)。在这一块里我们枚举k,我们再考虑k为4的情况,即先杀待杀编号为4的待杀羊,也就是图中的7号。
我们现在杀死7号
它被杀的消息开始向两侧扩散,我们只考察左侧,右侧同理。
还没轮到6号,继续扩散。
5号我们根本不会去杀,继续扩散。
4号与6号同理。
那会影响到3号吗?
为了解决这个问题我们看看下面这个情况
我们杀死3号时,它左侧的2号与4号显然被影响了,边界left与right外的羊也会被影响!
那这影响会在什么地方停止呢?当然是在遇到被杀死的🐏形成的空位时停止。
(当看到这里时可以回去看一句话概括了)
回到这张图
我们计算left=2,right=3(这个len为2)是因为我们想计算left=2,right=5(这个len为3)。并且在杀死待杀编号为4,即第7号时从left=2,right=5递归处理到了left=2,right=3。所以此时7号已死。也就是递归处理从left=2,right=5递归处理到left=2,right=3时,其左右边界外第一只待杀🐏都已经死了。
这才推出了这个杀死a[k]的代价:a[right+1]-a[left-1]-2
最后,left=right时思路中的表达式明显是不适用的,所以len=1我们要有初始化。
以及边界我们也要考虑,比如上图right=6时它已经是要杀的最后一只羊,此时a[right+1]是什么值呢?边界值即可,因为到最后一个都还没有遇到消息传不过去
的情况嘛,左侧也是同理。
最后附上代码,当然代码与思路都是大神写的,我只是做一个详细解释而已~
代码与思路来自这里
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
const int N = 110;
int f[N][N];
int a[N];
void solve()
{
int n,m;
cin >> n >> m;
for(int i=1;i<=m;i++)
cin >> a[i];
sort(a + 1,a + m + 1);
a[0] = 0;
a[m + 1] = n + 1;
for(int len=1;len<=m;len++)
for(int l=1;l + len - 1<=m;l++)
{
int r = len + l - 1;
if(len == 1)
{
f[l][r] = a[r + 1] - a[l - 1] - 2;
}
else
{
f[l][r] = 0x3f3f3f3f;
for(int i=l;i<=r;i++)
f[l][r] = min(f[l][r],a[r + 1] - a[l - 1] - 2 + f[l][i - 1] + f[i + 1][r]);
}
}
cout << f[1][m] << endl;
}
int main(){
//freopen("data.in","r",stdin);
//freopen("data.out","w",stdout);
ios :: sync_with_stdio(false);
cin.tie(0);
int T;
cin >> T;
while(T--) solve();
return 0;
}