数据结构专题 - 解题报告 - G

本文介绍了如何利用线段树和前缀和解决数据结构问题,通过公式推导简化判断标准,阐述了区间推平的操作,并强调在处理过程中线段树维护区间最大值的优势。此外,还提到了二分查找优化和处理细节,如懒标记处理和区间和的计算技巧。

推出公式就能做,讲过题基本上难度也瞬间降低了一个等级。不过对于我这个初学者来说写起来还是很费劲(线段树教做人)。

讲下公式吧:
我们设读入的 i 位置的数组b[ ]前缀和为B[i]

区间推平位置的判断标准:a[i]+b[i]&lt;a[i+1]区间推平位置的判断标准 : a[i] + b[i] &lt;a[i+1]a[i]+b[i]<a[i+1]
即:a[i]+B[i]−B[i−1]&lt;a[i+1]即:a[i]+B[i]-B[i-1]&lt;a[i+1]a[i]+B[i]B[i1]<a[i+1]
即:a[i]−B[i−1]&lt;a[i+1]−B[i]即 :a[i]-B[i-1]&lt;a[i+1]-B[i]a[i]B[i1]<a[i+1]B[i]

只要简单处理b[ ]的下标即可统一a[i]和B[i]的下标,并且因为公式化后与b[ ]数组值完全没关系,所以在读入的时候直接处理出前缀和B[ ],但是在本次代码中我仍旧引用b[ ]的数组名。

处理操作1时需要找到第一个大于a[i]+b[i]+x的位置,然后区间推平,更新成a[i]+b[i]+x的大小,但是由于每次查询都是查询a[ ]数组的区间和,所以我们可以围绕a[ ]数组建树,然后每次因为只用判断位置区间推平,这样建树其实不会带来太多麻烦。由于区间内都是小于该值的,所以等于是在维护t[i]的区间最大值。
值得一提的是,我们更新后t[ ]会一直处于有序递增的状态。所以也就满足了二分查找的条件。找到给定值的位置的复杂度也降低了。O(logn)
操作2就是求个区间和,线段树基本操作。

几个细节注意一下
pushdown的时候如果遇到懒标记为-inf,则说明懒标记不能动,这和以往懒标记初始化为0不同,如果不跳过可能会影响到儿子的更新
我发现在推平的时候,发现如果是循环相加每个b[i]的值就会很慢,不如先求b[i]的前缀和sum[i],需要的时候差分相减就有区间和了。
二分查找后要更新时要减去当前b[x]值,因为之前二分查找的过程中是与tr[i]+b[x]比较,得到的key值相当于加过一遍b[x],所以到时候要减去一次。(我在这里对拍了半天本地都过不去)

#include<bits/stdc++.h>
#define maxn 100005
#define maxm 200005
#define FOR(a, b, c) for(int a=b; a<=c; a++)
#define hrdg 1000000007
#define inf 2147483647
#define llinf 9223372036854775807
#define ll long long
#define pi acos(-1.0)
#define ls p<<1
#define rs p<<1|1
using namespace std;

inline int read()
{
    char c=getchar();long long x=0,f=1;
    while(c<'0'||c>'9'){if(c=='-') f=-1; c=getchar();}
    while(c>='0'&&c<='9'){x=x*10+c-'0'; c=getchar();}
    return x*f;
}

int n, m, type, x, y;
ll key;
ll a[maxn], b[maxn], sum[maxn];
ll tr[maxn<<2], tag[maxn<<2];

void pushup(int p) {tr[p] = tr[ls] + tr[rs];}		//都是一般的线段树操作
void build(ll p, ll l, ll r){
    tag[p] = -inf;
    if(l == r)
    {
        tr[p] = a[l];
        return;
    }
    ll mid = (l+r) >> 1;
    build(ls, l, mid);
    build(rs, mid+1, r);
    pushup(p);
}
void load(ll p, ll l, ll r, ll k){
    tr[p] = sum[r] - sum[l-1] + (r-l+1) * k;    //差分推平
    tag[p] = k;
}
void pushdown(ll p, ll l, ll r){
    if(tag[p] != -inf)      //如果tag[p]仍为-inf证明没有更新这个点,不管它
    {
        ll mid = (l+r) >> 1;
        load(ls, l, mid, tag[p]);
        load(rs, mid+1, r, tag[p]);
        tag[p] = -inf;
    }
}
void update(ll nl, ll nr, ll l, ll r, ll p, ll k){
    if(nl > nr) return;
    if(nl <= l && nr >= r)
    {
        load(p, l, r, k);
        return;
    }
    pushdown(p, l, r);
    ll mid = (l + r) >> 1;
    if(nl <= mid)
        update(nl, nr, l, mid, ls, k);
    if(nr >= mid+1)
        update(nl, nr, mid+1, r, rs, k);
    pushup(p);
}
ll query(ll nl, ll nr, ll l, ll r, ll p){
    ll ret = 0;
    if(nl <= l && nr >= r)
        return tr[p];
    pushdown(p, l, r);
    ll mid = (l + r) >> 1;
    if(nl <= mid)
        ret += query(nl, nr, l, mid, ls);
    if(nr >= mid+1)
        ret += query(nl, nr, mid+1, r, rs);
    return ret;
}
ll bin_search(ll p, ll x){          
    key = query(p, p, 1, n, 1);     //先找到在tr[]中的位置
    ll l = p, r = n;        
    while(l < r)        //二分查找大于等于的点
    {
        ll mid = (l + r + 1) >> 1;
        if(b[mid] - b[p] + key + x > query(mid, mid, 1, n, 1))
            l = mid;
        else
            r = mid - 1;
    }
    return l;       //返回l==r时就是所求位置(一般操作)
}

int main()
{
    n = read();
    FOR(i, 1, n)
        a[i] = read();
    build(1, 1, n);
    FOR(i, 2, n)
    {
        b[i] = read();
        b[i] += b[i-1];         //直接处理成和的形式
    }
    FOR(i, 2, n)
        sum[i] = sum[i-1] + b[i];       //提前处理前缀和,后面方便推平
    m = read();
    while(m--)
    {
        type=read(); x=read(); y=read();
        if(type == 1)
        {
            ll pos = bin_search(x, y);
            update(x, pos, 1, n, 1, key-b[x]+y);    //要减去b[x]才是待加的k
        }
        else
            printf("%lld\n", query(x, y, 1, n, 1)); 
    }
    return 0;
}

/*
3
1 2 3
1 -1
5
2 2 3
1 1 2
2 1 2
1 3 1
2 2 3
*/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值