线段树:最优清零方案

最优清零方案

问题描述

给定一个长度为 N N N 的数列 A 1 , A 2 , ⋯   , A N A_{1}, A_{2}, \cdots, A_{N} A1,A2,,AN 。现在小蓝想通过若干次操作将这个数列中每个数字清零。

每次操作小蓝可以选择以下两种之一:

  1. 选择一个大于 0 的整数, 将它减去 1 ;
  2. 选择连续 K K K 个大于 0 的整数, 将它们各减去 1 。

小蓝最少经过几次操作可以将整个数列清零?

输入格式

输入第一行包含两个整数 N N N K K K

第二行包含 N N N 个整数 A 1 , A 2 , ⋯   , A N A_{1}, A_{2}, \cdots, A_{N} A1,A2,,AN

输出格式

输出一个整数表示答案。

样例输入

4 2
1 2 3 4

样例输出

6

评测用例规模与约定

对于 20 20 \\% 20 的评测用例, 1 ≤ K ≤ N ≤ 10 1 \leq K \leq N \leq 10 1KN10

对于 40 40 \\% 40 的评测用例, 1 ≤ K ≤ N ≤ 100 1 \leq K \leq N \leq 100 1KN100

对于 50 50 \\% 50 的评测用例, 1 ≤ K ≤ N ≤ 1000 1 \leq K \leq N \leq 1000 1KN1000

对于 60 60 \\% 60 的评测用例, 1 ≤ K ≤ N ≤ 10000 1 \leq K \leq N \leq 10000 1KN10000

对于 70 70 \\% 70 的评测用例, 1 ≤ K ≤ N ≤ 100000 1 \leq K \leq N \leq 100000 1KN100000

对于所有评测用例, 1 ≤ K ≤ N ≤ 1000000 , 0 ≤ A i ≤ 1000000 1 \leq K \leq N \leq 1000000,0 \leq A_{i} \leq 1000000 1KN1000000,0Ai1000000

解题思路

分析
1、每次只能对某个数字减去 1 1 1,或者对长度为 K K K的序列减去1
——单点修改、区间修改

2、从最左端出发,即区间 [ 1 , K ] [1,K] [1,K],如果对该区间进行减法,则最多只能减去该区间的最小值。如果区间最小值为0,则无法进行减法,那也就是第一个数字只能一次一次消去。
——如何选择单点修改和区间修改

根据上述分析,我们只需要从左往右枚举左端点 l l l,此时可以处理的区间为 [ l , l + k − 1 ] [l,l+k-1] [l,l+k1],需要求出区间最小值 m i mi mi

  • 如果 m i = = 0 mi==0 mi==0,答案加上此时的 a [ l ] a[l] a[l],说明无法以该起点进行区间消除,只能单个消除。
  • 如果 m i ≠ 0 mi\neq0 mi=0,答案加上 m i mi mi,然后对区间 [ l , l + k − 1 ] [l,l+k-1] [l,l+k1]进行区间减法,统一减去 m i mi mi。然后再加上此时的 a [ l ] a[l] a[l],因为此时剩下的 a [ l ] a[l] a[l]部分也只能单个消除。

AC_Code(Python)

# -*- coding: utf-8 -*-
# @Author : BYW-yuwei
# @Software: python3.8.6
maxn = 1000000 + 10
tree_mi = [0] * (maxn * 4)
tree_add = [0] * (maxn * 4)

n, k = list(map(int, input().split()))
a = list(map(int, input().split()))
a = [0, *a]

#线段树模板
#利用左右儿子信息更新节点o
def push_up(o):
    tree_mi[o] = min(tree_mi[o << 1], tree_mi[o << 1 | 1])

#利用节点o的lazy标记add更新左右儿子
def push_down(o):
    if tree_add[o] != 0:
        tree_add[o << 1] += tree_add[o]
        tree_mi[o << 1] += tree_add[o]
        tree_add[o << 1 | 1] += tree_add[o]
        tree_mi[o << 1 | 1] += tree_add[o]
        tree_add[o] = 0

#建树
def build(o, l, r):
    tree_add[o] = 0
    if l == r:
        tree_mi[o] = a[l]
        return
    mid = (l + r) >> 1
    build(o << 1, l, mid)
    build(o << 1 | 1, mid + 1, r)
    push_up(o)

#查询区间[L,R]的最小值
def query(o, l, r, L, R):
    if L <= l and r <= R:
        return tree_mi[o]
    push_down(o);
    mid = (l + r) >> 1
    ans = 1000000000;
    if L <= mid:
        ans = min(ans, query(o << 1, l, mid, L, R))
    if R > mid:
        ans = min(ans, query(o << 1 | 1, mid + 1, r, L, R))
    return ans

#区间更新[L,R]统一加上val
def update(o, l, r, L, R, val):
    if L <= l and r <= R:
        tree_mi[o] += val
        tree_add[o] += val
        return
    push_down(o);
    mid = (l + r) >> 1
    if L <= mid:
        update(o << 1, l, mid, L, R, val)
    if R > mid:
        update(o << 1 | 1, mid + 1, r, L, R, val)
    push_up(o);


build(1, 1, n)
ans = 0
for i in range(1, n - k + 2):
    #查询区间[i, i+k-1]的最小值
    mi = query(1, 1, n, i, i + k - 1)
    if mi == 0:                     #无法进行区间消除
        #res表示当前的a[i]
        res = query(1, 1, n, i, i)
        #把当前的a[i]置为0
        update(1, 1, n, i, i, -res)
        ans += res
    else:
        ans += mi
        #区间消除
        update(1, 1, n, i, i + k - 1, -mi)
        #res表示当前的a[i]
        res = query(1, 1, n, i, i)
        #把当前的a[i]置为0
        update(1, 1, n, i, i, -res)
        ans += res
for i in range(n - k + 2, n + 1):
    ans += query(1, 1, n, i, i)
print(ans)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

是雨玮a

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值