CF1380D.Berserk And Fireball 【2000】你值得学习的【思维】+【模拟】+【贪心】

链接

CF1380D.Berserk And Fireball

题意

  • 存在 n n n 个战士
  • 两种技能
    • 第一种,消费 x x x 点魔力值,消灭连续的 k k k 个战士
    • 第二种,消费 y y y 点魔力值,选择两个连续战士,战斗力大的消灭战斗力小的
  • 经过消费魔力的多次两种操作,使得 a a a 数组变成 b b b 数组,问最少需要花费的魔力值是多少?
  • 若无法使 a a a 数组变成 b b b 数组,则输出 − 1 -1 1
  • 两种操作图示方框颜色是不相同的,希望能够有好的阅读体验。

样例输入
5 2
5 2 3
3 1 4 5 2
3 5
样例输出
8

解释

  • 原数组
    在这里插入图片描述
  • 1、4 两元素恰好长度为2,于是我们进行1操作,花费x(5)魔力值消灭连续的k(2)个战士
    在这里插入图片描述
  • 5、2两元素的小值恰好是我们需要删除的2,且单一2元素的长度为1不合适进行操作1,所以选择操作2进行,花费魔力值3

在这里插入图片描述
在这里插入图片描述

  • 最后所花费的魔力值为 8 = 5 + 3

以上图示符合样例1的输出情况

无解情况

样例输入
4 4
5 1 4
4 3 1 2
2 4 3 1
样例输出
-1

很明显相同长度的两个数组,元素的相对位置完全不同已经不能通过操作1、2进行改变了,因为操作1、2都没有改变元素的相对位置。

疑问1

为什么不进行操作2?(那好,我们来尝试下操作2消灭【1,4】的情况)
在这里插入图片描述
结果:在这里插入图片描述
总花费:6魔力值>5魔力值(操作1)

归纳:这么说来操作2也不是不行,主要取决于我操作1和操作2的魔力值单次消耗情况和能否进行删除(因为在这样例中是恰好 3 > 1 3>1 3>1 4 < 5 4<5 4<5,刚好小的都是需要删除的)

想法:因此我们需要根据各种操作的魔力值消费情况进行贪心。

题解

  1. 我们将该数组序列进行一下划分,划分为多个区间,这是为了根据不同区间的长度考虑进行操作1还是操作2,参考上面的疑问1。
  2. 我们设区间长度为 L L L
  3. L < k L< k L<k时,区间最大值大于左右端点,则无解,这是因为大值是你想要消灭的,你却没有办法通过操作2进行消灭,如果区间最大值不大于左右端点, 魔法消耗就是 L * y,(两个值消灭一个值,需要消灭长度为L,即L * y,这里不太理解可以看一下单调队列)
  4. L > = k L>=k L>=k时,区间最大值大于左右端点,那么我们可以进行操作2花费x的魔力值,消灭长度为k的战士。

注意:程序中途中的乘积会爆int

代码

/*
* n个战士
* 两种技能 1.x 魔力值 ,连续消灭k个战士 2.y魔力值,选择两个连续战士,战斗力大的消灭战斗力小的
* ai -> bi
*/
const int maxn = 2e5 + 50;
int a[maxn], b[maxn];
LL x, k, y;
int n, m;
bool solve(int l, int r, LL &ans){
    bool judge = false; //当L< k时,区间最大值大于左右端点,则无解;
    if (l > r) return true;
    //我们区间的有解和无解是通过判断区间中最大值的情况来解决的,因此我们需要寻找最大值
    int maxIndex = l;
    int L = r - l + 1; //区间长度
    FOR_1(i, l, r) if (a[i] > a[maxIndex]) maxIndex = i;

    //和左右端点值进行比较
    if (l - 1 >= 1 && a[l - 1] > a[maxIndex]) judge = true;
    if (r + 1 <= n && a[r + 1] > a[maxIndex]) judge = true;
    if (L < k && !judge) return false;
    
    //need remove
    int need = L % k;
    ans += need * y;
    L -= need;
    //疑问1的解决办法
    if (y * k >= x) { ans += L / k * x; }
    else if(judge) { ans += L * y; } 
    else { //其中长度k进行操作1,剩下进行操作2
        ans += (L - k) * y + x;
    }
    
    return true;
}
int main(){
    RD(n, m); RD(x, k, y);
    FOR_1(i, 1, n) RD(a[i]); FOR_1(i, 1, m) RD(b[i]);
    int i = 1, j = 1;
    LL ans = 0; //需要花费的魔力值
    //把b的部分都筛选出来,也就是b的下标j需要走到
    int p = 0;
    while(j <= m){
        while(i <= n && a[i] != b[j]) i++;
        if (i > n) return printf("-1\n") * 0; //b都没全部得出a就没了,说明存在问题不可能成立,直接-1就好
        if (!solve(p + 1, i - 1, ans)) return printf("-1\n") * 0;
        p = i, j++;
    }
    if (!solve(p + 1, n, ans)) return printf("-1\n") * 0;
    printf("%lld\n", ans);

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值