蓝桥杯 算法提高 宰羊(区间DP)

在过去的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;
}
  • 11
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值