【线段树】【树】[BZOJ4293][HYSBZ4293][PA2015]Siano

题目描述

注:此题为BZOJ的权限题目

Description

农夫Byteasar买了一片n亩的土地,他要在这上面种草。
他在每一亩土地上都种植了一种独一无二的草,其中,第i亩土地的草每天会长高a[i]厘米。
Byteasar一共会进行m次收割,其中第i次收割在第d[i]天,并把所有高度大于等于b[i]的部分全部割去。Byteasar想知道,每次收割得到的草的高度总和是多少,你能帮帮他吗?

Input

第一行包含两个正整数n,m(1<=n,m<=500000),分别表示亩数和收割次数。
第二行包含n个正整数,其中第i个数为ai,依次表示每亩种植的草的生长能力。
接下来m行,每行包含两个正整数d[i],bi,依次描述每次收割。
数据保证d[1]

Output

输出m行,每行一个整数,依次回答每次收割能得到的草的高度总和。

样例输入

4 4
1 2 4 3
1 1
2 2
3 0
4 4

样例输出

6
6
18
0

题目分析

就这么说吧,我觉得这题的代码难度比这题的思维难度要难。我们可以很容易的发现,如果我们首先将所有的土地按照杂草的生长速度排序之后进行求解得到的答案其实是不变的,那么我们第一步先排序,然后我们发现无论生长到第几天,杂草的高度序列总是不下降的,同理有割掉草之后,继续生长杂草的序列仍然是不下降的,那么我们可以很容易的得到算法:

  1. 首先生长后,在线段树上二分找到我们另当前杂草大于询问的位置
  2. 输出=(所有杂草高度之和-(大于的土地数量*询问的高度))
  3. 将得到的位置后面的杂草(因为全部高于当前)赋值为当前询问的高度

这里都看上去很正常,复杂度似乎也可以接受 O(nlogn) ,那么我们开始写。。。写着写着我们发现如果要统计区间每一天增加多少怎么办。。。可以统计当前给定 Ai 的前缀和 di 对于一个区间 [l,r] 我们有每天生长 (drdl1) 那么就可以得到 c 天后生长多少了,但是我们同样可以发现,我们还需要维护一个b表示当前的树我们需要赋值成多少。。。可以写了?不能
我们可以发现,当子树中存在一个 b 同时当前也有一个c要push_down的时候,我们两边都不能抛弃那么我们令 a 表示是否保留当前值那么我们对于每一段就有

tree[u].sum=tree[u].sum×tree[u].a+(tree[u].rtree[u].l+1)×tree[u].b+tree[u].c×(dtree[u].rdtree[u].l1)
同理最大值一定位于区间右边那么最大值为

tree[u].Max=tree[u].Max×tree[u].a+tree[u].b+tree[u].c×Atree[u].r
但是我们对于处理下方就要分类讨论了,发现如果当前为 u ,下一层为uson那么我们可以发现当 tree[u].a==0 的时候我们有完全覆盖,那么可以直接
tree[uson].a=0tree[uson].b=tree[u].btree[uson].c=tree[u].c
然后根据上面的公式计算出 sum Max

然后我们考虑当 tree[u].a==1 的情况,我们可以发现 1 代表保留原来的内容,那么我们保持原来的a不变, bc 累加(因为满足分配律)所以我们可以得到

tree[uson].b=tree[uson].b+tree[u].btree[uson].c=tree[uson].c+tree[u].c
那么我们可以发现这个时候增加的 b 其实只有tree[u].b也可以发现增加的 c 也只有tree[u].c的部分,那么
tree[uson].Max=tree[uson].Max+tree[u].b+tree[u].c×Atree[uson].rtree[uson].sum=tree[uson].sum+tree[u].b×(tree[uson].rtree[uson].l+1)+tree[u].c×(dtree[uson].rdtree[uson].l1)

当然对于左右区间搞完之后就可以将当前的设置为

tree[u].a=1tree[u].b=0tree[u].c=0

最艰难的部分已经过去push_up就和平时的push_up没有什么不同。可以开始写了。

代码

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int MAXN = 500000;
long long a[MAXN+10], up[MAXN+10];
int aa, cc;
long long bb;
struct Tree{
    int l, r;
    long long Max, sum, b, c;
    bool aa;
    void operator+=(const Tree& an){
        if(an.aa){
            b += an.b; c += an.c;
            Max += an.b + an.c*a[r];
            sum += an.b*(r-l+1)+an.c*(up[r]-up[l-1]);
        }else{
            aa = 0; b = an.b; c = an.c;
            Max = b + c * a[r];
            sum = b * (r - l + 1) + (up[r] - up[l-1]) * c;
        }
    }
}tree[MAXN*4+10];
void recalc(int u, int l, int r){
    tree[u].sum = tree[u].sum * tree[u].aa + tree[u].b * (r - l + 1) + tree[u].c * (up[r] - up[l-1]);
    tree[u].Max = tree[u].Max * tree[u].aa + tree[u].b + tree[u].c * up[r];
}
void push_down(int u){
    tree[u*2+1] += tree[u];
    tree[u*2] += tree[u];
    tree[u].aa = 1, tree[u].b = 0, tree[u].c = 0;
}
void push_up(int u){
    tree[u].Max = tree[u*2+1].Max;
    tree[u].sum = tree[u*2].sum + tree[u*2+1].sum;
    tree[u].aa = 1;
    tree[u].b = tree[u].c = 0;
}
int Findpos(int u, long long v){
    if(tree[u].l == tree[u].r) return tree[u].l;
    push_down(u);
    if(tree[u*2].Max >= v) return Findpos(u*2, v);
    return Findpos(u*2+1, v);
}
long long query(int u, int pos){
    if(pos <= tree[u].l) return tree[u].sum;
    int mid=(tree[u].l+tree[u].r)>>1;
    push_down(u);
    long long ret = 0;
    if(pos <= mid) ret += query(u*2, pos);
    ret += query(u*2+1, pos);
    return ret;
}
void reset(int u, int pos, long long v){
    if(pos <= tree[u].l){
        tree[u].aa = tree[u].c = 0; tree[u].b = v;
        tree[u].Max = v;
        tree[u].sum = (tree[u].r - tree[u].l + 1) * v;
        return ;
    }
    int mid = (tree[u].l + tree[u].r) >> 1;
    push_down(u);
    reset(u*2+1, pos, v);
    if(pos <= mid) reset(u*2, pos, v);
    push_up(u);
}
void build(int u, int l, int r){
    tree[u].l = l, tree[u].r = r;
    tree[u].aa = 1;
    if(l == r) return ;
    int mid = (l + r) >> 1;
    build(u*2, l, mid);
    build(u*2+1, mid+1, r);
}
int main(){
    int n, m;
    long long d, b, ud=0;
    scanf("%d%d", &n, &m);
    build(1, 1, n);
    for(int i=1;i<=n;i++) scanf("%I64d", &a[i]);
    sort(a+1, a+1+n);
    for(int i=1;i<=n;i++) up[i] = up[i-1] + a[i];
    for(int i=1;i<=m;i++){
        scanf("%I64d%I64d", &d, &b);
        long long day = d - ud;
        tree[1].c += day;
        tree[1].Max += day * a[n];
        tree[1].sum += day * up[n];
        if(tree[1].Max <= b){
            ud = d;
            printf("0\n");
            continue;
        }
        long long pos = Findpos(1, b);
        printf("%I64d\n", query(1, pos) - 1LL * b * (n-pos+1));
        reset(1, pos, b);
        ud = d;
    }

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值