UOJ 228 基础数据结构练习题

线段树

多写点骗点访问量

加法好说,开平方就很气了,因为开平方的信息不能合并。这样就导致常规的线段树,分块之类的维护数列和的方法不能用。我们只能另辟蹊径,找寻性质。

我们发现开平方操作会让数字减少得很快,大概六七次就能把一个数变成1。这给我一种在不特意构造数据的情况下,最终数字很容易变得大部分都一样的感觉。

如果没有加法,那只要判断一下区间里有没有非1的,有就暴力做,这样复杂度没问题。考虑有加法,加法会让数字又变得不一样,仔细观察我们发现对于区间修改[l,r],l和r处相邻数字差变大,也就是只有两个地方发生变化。

那我们用线段树维护序列。如果区间里数字都一样就按常规线段树的来做,否则递归进左右子树。不考虑递归进子树的话,时间复杂度是 O(mlogn) 。考虑递归进去,当且仅当有一次修改的端点碰到了这个节点,最多 m 个修改,其实也就是最多有O(mlogn)个结点会这样。递归进去的操作相当于回收标记,最多需要7次就可以回收完毕,因此总 O(7nlogn)

有一种神奇的数据:343434,开方完是121212,加2后又是343434,这样的特判一下- -

这种基于均摊时间复杂度的线段树好神奇啊……适用的范围大概是一些操作会改变序列的平衡性,另一些操作会恢复序列的平衡性的题目吧……

#include<cstdio>
#include<cmath>
#include<algorithm>
#define N 100005
using namespace std;
namespace runzhe2000
{
    typedef long long ll;
    typedef double db;
    int n, m, a[N];
    struct seg
    {
        ll sum, mx, mi, tag;
    }t[N*5];
    void pushup(int x)
    {
        t[x].sum = t[x<<1].sum + t[x<<1|1].sum;
        t[x].mx = max(t[x<<1].mx, t[x<<1|1].mx);
        t[x].mi = min(t[x<<1].mi, t[x<<1|1].mi);
    }
    void pushdown(int x, int l, int r)
    {
        if(!t[x].tag) return; int mid = (l+r)>>1; ll &tag = t[x].tag;
        t[x<<1].tag += tag; t[x<<1|1].tag += tag;
        t[x<<1].sum += tag * (mid - l + 1); t[x<<1|1].sum += tag * (r - mid);
        t[x<<1].mx += tag; t[x<<1|1].mx += tag;
        t[x<<1].mi += tag; t[x<<1|1].mi += tag; 
        tag = 0;
    }
    void build(int x, int l, int r)
    {
        if(l == r){t[x].sum = t[x].mx = t[x].mi = a[l]; t[x].tag = 0; return;}
        int mid = (l+r)>>1; build(x<<1,l,mid); build(x<<1|1,mid+1,r); pushup(x);
    }
    void add(int x, int l, int r, int ql, int qr, int v)
    {
        if(ql <= l && r <= qr){t[x].tag += v; t[x].sum += (ll)(r-l+1) * v; t[x].mi += v; t[x].mx += v; return;}
        pushdown(x,l,r); int mid = (l+r)>>1; if(ql <= mid) add(x<<1,l,mid,ql,qr,v); if(mid < qr ) add(x<<1|1,mid+1,r,ql,qr,v); pushup(x);
    }
    void modi(int x, int l, int r, int ql, int qr)
    {
        if(ql <= l && r <= qr && t[x].mx - t[x].mi == (int) sqrt((db)t[x].mx) - (int) sqrt((db)t[x].mi))
        {
            ll v = (ll)sqrt((double)t[x].mx) - t[x].mx;
            t[x].tag += v; t[x].sum += (ll)(r-l+1) * v; t[x].mi += v; t[x].mx += v;
            return;
        }
        pushdown(x,l,r); int mid = (l+r)>>1; if(ql <= mid) modi(x<<1,l,mid,ql,qr); if(mid < qr ) modi(x<<1|1,mid+1,r,ql,qr); pushup(x);
    }
    ll query(int x, int l, int r, int ql, int qr)
    {
        if(ql <= l && r <= qr) return t[x].sum; int mid = (l+r)>>1; ll ret = 0;
        pushdown(x,l,r);
        if(ql <= mid) ret += query(x<<1,l,mid,ql,qr);
        if(mid <  qr) ret += query(x<<1|1,mid+1,r,ql,qr);
        return ret;
    } 
    void main()
    {
        scanf("%d%d",&n,&m);
        for(int i = 1; i <= n; i++) scanf("%d",&a[i]); build(1,1,n);
        for(int opt, l, r, x; m--; )
        {
            scanf("%d",&opt);
            if(opt == 1) // add
            {
                scanf("%d%d%d",&l,&r,&x);
                add(1,1,n,l,r,x);
            }
            else if(opt == 2) // sqrt
            {
                scanf("%d%d",&l,&r);
                modi(1,1,n,l,r);
            }
            else 
            {
                scanf("%d%d",&l,&r);
                printf("%lld\n",query(1,1,n,l,r));
            }
        }
    }
}
int main()
{
    runzhe2000::main();
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值