[2019长沙长郡中学集训]加法

题面描述

给定一个\(n\)阶排列\(b\),要求维护一个初值全为\(0\)的数组\(\{a_i\}\),支持\(q\)次如下操作:

  • 给出\(l,r\),将\(a_l,a_{l+1},......,a_{r-1},a_r\),全部+1
  • 给出\(l,r\),查询\(\sum_{i=l}^{r} \lfloor \frac{{b[i]}}{a[i]} \rfloor\)

    输入格式

    第一行输入两个整数\(n,q\)
    第二行\(n\)个正整数表示排列\(b\)
    接下来\(q\)行,每行三个正整数\(op,l,r,op=1\)表示第一种操作,\(op=2\)表示第二种操作

    输出格式

    对于每次询问,输出一行一个非负整数表示答案。

    数据规模

    对于$%30 $的数据,\(1\leq n,q \leq 3000.\)
    对于另外\(\%30\)的数据,当\(op=2\)时,有\(l=1,r=n\)
    对于\(\%100\)的数据,\(1\leq n,q \leq 3\times 10^5,op\in\{1,2\},1\leq l \leq r \leq n.\)

    样例输入

    5 10
    1 5 2 4 3
    1 1 4
    2 1 4
    1 2 5
    1 3 5
    2 1 5
    1 2 4
    2 1 4
    1 2 5
    1 2 2
    2 1 5

    样例输出

    1
    2
    4
    6

    题解
    考试的时候爆掉了。。。卡常是真的恶心QAQ
    先考虑部分分的做法
    对于3000以内的数据,我们直接暴力循环。比较闲的同学可以考虑写两个线段树,一个用来更新a数组的值,另一个用来维护a[i]/b[i]。
    对于查询操作恒查询整个区间的数据,我们可以只维护一个线段树,用于维护a数组,并区间查询整个区间的ans即可。
    ---------------------------以下是正解部分-----------------------------
    观察原式可以发现,我们最后的取值要向下取整,则每一个位置\(i\)产生更多贡献的唯一办法就是使a[i]成为b[i]的k倍(k>0)。因此,我们用一个线段树记录b[i]的值,用数组记录b的原值,同时用另一棵线段树维护答案,每次op=1时使区间内的每一个b[i]全部-1。当b[i]=0时我们使当前位的贡献+1,并重置b[i]的值为初始值。每次单独变换的复杂度为\(O(\log n)\),每次查询时递归进第二棵线段树中查找,因此总的复杂度是\(O(q \log^2n)\)
    为什么要用减法而不是加法呢?
    如果用加法运算我们需要同时维护maxa和minb的值,而如果用减法只用维护一个min就好了
#include<bits/stdc++.h>
#define il inline
#define re register
typedef long long ll;
using namespace std;
const int maxn = 3e5 + 10;
int n, q, ans;
int b[maxn];
int trmin[maxn<<2], tradd[maxn<<2], lazy[maxn<<2];
//
il void init() {
    memset(lazy, 0, sizeof(lazy));
    memset(trmin, 0, sizeof(trmin));
    memset(tradd, 0, sizeof(tradd));
}
//
il void pushup(int id) {
    trmin[id] = min(trmin[id<<1], trmin[id<<1|1]);
    tradd[id] = tradd[id<<1] + tradd[id<<1|1];
    return ;
}
//
il void build(int id, int l, int r) {
    if(l == r) {
        trmin[id] = b[l];
        return;
    }
    int mid = (l+r)>>1;
    build(id<<1, l, mid);
    build(id<<1|1, mid+1, r);
    pushup(id);
}
il void pushdown(int id) {
    if (lazy[id]) {
        lazy[id << 1] += lazy[id];
        lazy[id << 1 | 1] += lazy[id];
        trmin[id << 1] -= lazy[id];
        trmin[id << 1 | 1] -= lazy[id];
        lazy[id] = 0;
    }
}
il void update(int id, int l, int r, int x, int y) {
    if(trmin[id] > 1 && x <= l && r <= y) {
        lazy[id]++;
        trmin[id]--;
        return ;
    }
    if(trmin[id] == 1 && l == r) {
        tradd[id]++;
        trmin[id] = b[l];
        lazy[id] = 0;
        return ;
    }
    pushdown(id);
    int mid = (l + r) >> 1;
    if (x <= mid)
        update(id << 1, l, mid, x, y);
    if (y > mid)
        update(id << 1 | 1, mid + 1, r, x, y);
    pushup(id);
}
il int query(int id, int l, int r, int x, int y) {
    if(x <= l && r <= y) return tradd[id];
    if(trmin[id] == 0) update(1, 1, n, x, y);
    pushdown(id);
    int sum = 0;
    int mid = (l+r) >> 1;
    if(x <= mid) sum += query(id<<1, l, mid, x, y);
    if(y > mid) sum += query(id<<1|1, mid+1, r, x, y);
    pushup(id);
    return sum;
}
signed main(){
    init();
    scanf("%d%d", &n, &q);
    for(re int i = 1; i <= n; ++i) scanf("%d", &b[i]);
    build(1, 1, n);
    int op, l, r;
    while(q--) {
        scanf("%d%d%d", &op, &l, &r);
        if(op == 1)
            update(1, 1, n, l, r);
        else
            printf("%d\n", query(1, 1, n, l, r));
    }
    return 0;
} 

转载于:https://www.cnblogs.com/Chen574118090/p/11563154.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值