hdu5828 Rikka with Sequence (线段树:区间开根+区间求和+区间加减)

题意:

对一个n元素序列进行三种操作:
1 l r x : 对a[l,r]之间所有元素都加上x
2 l r: 对a[l,r]之间所有元素开根号
3 l r: 输出a[l,r]之间的元素和

(1<=n,m<=1e5 , 1<=A[i],x<=1e5)

分析:

这道题难点就在“区间开根”这一操作。因为如果像正常的区间更新一样,用懒惰标记,我们会发现无法快速更新区间信息,也就是说无法快速得到它询问的区间和。

所以,类似“区间取模”,需要分析开根操作的特点。我们发现,即使是两个差很大的数,都可以在很少次数的开根计算后趋近于相等,最后都变成1。

当一个区间内的数都相等会怎样呢?

  • 这时区间开根操作就变为了区间减法,开根就相当于所有的数都减了一个数。

当一个区间的极差是1会怎样呢?开根后会有两种情况:

  • 极差仍然为1。那开根就相当于都减去一个数。
  • 极差变为0。下次开根就是区间减法。(此处也可看作是区间覆盖)

若区间极差大于1,那就递归暴力访问更新。直至变成上面的情况。

注意点:

  1. laz标记用来标记“区间加减”的时候,初始值一定是 0 而不是 -1,-1 的话在第一次laz[rt] += x 时会出错。
  2. 试了一次输入外挂, 发现确实会快300ms

代码:

#include <iostream>
#include <algorithm>
#include <queue>
#include <stack>
#include <vector>
#include <set>
#include <cmath>
#include <cstdlib>
#include <cstring>
#include <cstdio>
using namespace std;
#define ms(a,b) memset(a,b,sizeof(a))
typedef long long ll;
const int MAXN = 1e5 + 5;
const double EPS = 1e-8;
const int INF = 0x3f3f3f3f;
#define lson rt*2,l,(l+r)/2
#define rson rt*2+1,(l+r)/2+1,r
ll mx[MAXN << 2], mi[MAXN << 2], sum[MAXN << 2], laz[MAXN << 2];
int n;
void pushup(int rt) {
    sum[rt] = sum[rt << 1] + sum[rt << 1 | 1];
    mx[rt] = max(mx[rt << 1], mx[rt << 1 | 1]);
    mi[rt] = min(mi[rt << 1], mi[rt << 1 | 1]);
}
void pushdown(int rt, int l, int r) {
    if (laz[rt] != 0) {
        int mid = (l + r) / 2;
        laz[rt << 1] += laz[rt];    laz[rt << 1 | 1] += laz[rt];
        sum[rt << 1] += laz[rt] * (mid - l + 1);
        sum[rt << 1 | 1] += laz[rt] * (r - mid);
        mx[rt << 1] += laz[rt]; mx[rt << 1 | 1] += laz[rt];
        mi[rt << 1] += laz[rt]; mi[rt << 1 | 1] += laz[rt];
        laz[rt] = 0;
    }
    return;
}
void build(int rt, int l, int r) {
    if (l == r) {
        scanf("%lld", &sum[rt]);
        mx[rt] = mi[rt] = sum[rt];
        return;
    }
    build(lson);
    build(rson);
    pushup(rt);
}
void update(int L,int R,int rt, int l, int r, ll x) {
    if (L <= l && R >= r) {
        laz[rt] += x;
        sum[rt] += x * (r - l + 1);
        mx[rt] += x;
        mi[rt] += x;
        return;
    }
    pushdown(rt, l, r);
    if (L <= (l + r) / 2)   update(L,R,lson, x);
    if (R > (l + r) / 2)    update(L,R,rson, x);
    pushup(rt);
}
void sub(int rt, int l, int r, ll x) {
    laz[rt] -= x;
    sum[rt] -= (r - l + 1) * x;
    mx[rt] -= x;
    mi[rt] -= x;
}
void change(int L,int R,int rt, int l, int r) {
    if (L <= l && R >= r) {
        ll a = mx[rt], b = mi[rt];
        if ((a - b) <= 1 && ((ll)sqrt(a) - (ll)sqrt(b)) == (a - b)) {
            sub(rt, l, r, a - (ll)sqrt(a));
            return;
        }
    }
    pushdown(rt, l, r);
    if (L <= (l + r) / 2)   change(L,R,lson);
    if (R > (l + r) / 2)    change(L,R,rson);
    pushup(rt);
}
ll query(int L,int R,int rt, int l, int r) {
    if (L <= l && R >= r) {
        return sum[rt];
    }
    pushdown(rt, l, r);
    ll ans = 0;
    if (L <= (l + r) / 2)   ans += query(L,R,lson);
    if (R > (l + r) / 2)    ans += query(L,R,rson);
    pushup(rt);
    return ans;
}
int main() {
    int T, m;
    scanf("%d", &T);
    while (T--) {
        scanf("%d%d", &n, &m);
        build(1, 1, n);
        ms(laz, 0);
        while (m--) {
            int op,L,R;
            ll x;
            scanf("%d%d%d", &op, &L, &R);
            if (op == 1)    {scanf("%lld", &x); update(L,R,1, 1, n, x);}
            else if (op == 2)   change(L,R,1, 1, n);
            else    printf("%lld\n", query(L,R,1, 1, n));
        }
    }

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值