【UOJ#228】基础数据结构练习题 线段树

#228. 基础数据结构练习题

题目链接:http://uoj.ac/problem/228

Solution

这题由于有区间+操作,所以和花神还是不一样的。 花神那道题,我们可以考虑每个数最多开根几次就会成1,而这个必须利用开根的性质

我们维护区间最大、最小、和。区间加操作可以直接做。

区间开方操作需要特殊考虑。

首先对于一个区间,如果这个区间的所有数取$x=\left \lfloor \sqrt{x} \right \rfloor$值一样,那么就可以直接区间覆盖。

分析上述过程,一个区间可以直接覆盖,当这个区间的差值满足一个特定的范围。 而每次开方这个差值就会减少,可以证明这样开方$lg^{2}$次就会全部为1

所以剩下的我们就可递归下去。

这样的话,区间+操作,就相当于重置了这个差值,所以复杂度还是科学的。

但是有一种情况出现问题。

上述是每次开方后,差值减小,但是有开方后差值不变的情况。  例如 3 4 3 4 3 4 3 4

即$a$,$b$当$b$为完全平方数,$a=b-1$时。这样开方完差值还是1,然后区间+2就又变回来了。 这样上述就卡成了暴力。

那么我们把这种情况特殊考虑。 这样可以转化为一个区间-的操作。剩下的暴力递归,这样就可以了。

时间复杂度是$O(NlogNlg^2{N})$

Code

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
#define LL long long
inline int read()
{
    int x=0,f=1; char ch=getchar();
    while (ch<‘0‘ || ch>‘9‘) {if (ch==‘-‘) f=-1; ch=getchar();}
    while (ch>=‘0‘ && ch<=‘9‘) {x=x*10+ch-‘0‘; ch=getchar();}
    return x*f;
}
#define MAXN 100010
int N,M,a[MAXN];
namespace SegmentTree
{
    struct SegmentTreeNode{int l,r,cov; LL tag,sum,maxx,minx;}tree[MAXN<<2];
    #define ls now<<1
    #define rs now<<1|1
    inline void Update(int now)
    {
        tree[now].sum=tree[ls].sum+tree[rs].sum;
        tree[now].maxx=max(tree[ls].maxx,tree[rs].maxx);
        tree[now].minx=min(tree[ls].minx,tree[rs].minx);
    }
    inline void cover(int now,int D)
    {
        tree[now].cov=D; tree[now].tag=0;
        tree[now].minx=tree[now].maxx=D;
        tree[now].sum=D*(tree[now].r-tree[now].l+1);
    }
    inline void modify(int now,LL D)
    {
        tree[now].tag+=D;
        tree[now].minx+=D; tree[now].maxx+=D; tree[now].sum+=(tree[now].r-tree[now].l+1)*D;
    }
    inline void PushDown(int now)
    {
        if (tree[now].l==tree[now].r) return;
        if (tree[now].cov!=-1)
            cover(ls,tree[now].cov),cover(rs,tree[now].cov),tree[now].cov=-1;
        if (tree[now].tag!=0)
            modify(ls,tree[now].tag),modify(rs,tree[now].tag),tree[now].tag=0;
    }
    inline void BuildTree(int now,int l,int r)
    {
        tree[now].l=l; tree[now].r=r; tree[now].cov=-1;
        if (l==r) {tree[now].sum=tree[now].maxx=tree[now].minx=a[l]; return;}
        int mid=(l+r)>>1;
        BuildTree(ls,l,mid); BuildTree(rs,mid+1,r);
        Update(now);
    }
    inline void Modify(int now,int L,int R,int D)
    {
        int l=tree[now].l,r=tree[now].r;
        PushDown(now);
        if (L<=l && R>=r) {modify(now,D); return;}
        int mid=(l+r)>>1;
        if (L<=mid) Modify(ls,L,R,D);
        if (R>mid) Modify(rs,L,R,D);
        Update(now);
    }
    inline void Change(int now,int L,int R)
    {
        int l=tree[now].l,r=tree[now].r;
        PushDown(now);
        if (L<=l && R>=r)
            {
                if ((int)sqrt(tree[now].maxx)==(int)sqrt(tree[now].minx))
                    {cover(now,(int)sqrt(tree[now].maxx)); return;}
                if (tree[now].maxx==tree[now].minx+1)
                    {modify(now,(int)sqrt(tree[now].minx)-tree[now].minx); return;}
                if (l!=r) Change(ls,L,R),Change(rs,L,R);
                Update(now);
                return;
            }
        int mid=(l+r)>>1;
        if (L<=mid) Change(ls,L,R);
        if (R>mid) Change(rs,L,R);
        Update(now);
    }
    inline LL Query(int now,int L,int R)
    {
        int l=tree[now].l,r=tree[now].r;
        PushDown(now);
        if (L<=l && R>=r) return tree[now].sum;
        int mid=(l+r)>>1; LL re=0;
        if (L<=mid) re+=Query(ls,L,R);
        if (R>mid) re+=Query(rs,L,R);
        return re;
    }
}
int main()
{
    N=read(),M=read();
    for (int i=1; i<=N; i++) a[i]=read();
    SegmentTree::BuildTree(1,1,N);
    while (M--)
        {
            int opt=read(),l=read(),r=read(),D;
            switch (opt)
                {
                    case 1: D=read(),SegmentTree::Modify(1,l,r,D); break;
                    case 2: SegmentTree::Change(1,l,r); break;
                    case 3: printf("%lld\n",SegmentTree::Query(1,l,r)); break;
                }
//            for (int i=1; i<=N; i++) printf("%d  ",SegmentTree::Query(1,i,i)); puts("=================");
        }
    return 0;
}
时间: 2024-10-24 18:13:38

【UOJ#228】基础数据结构练习题 线段树的相关文章

UOJ - #228. 基础数据结构练习题

题意: 一个区间支持三种操作,区间加,区间开根号和区间求和. 题解: 线段树的做法.对于区间开根号操作,如果要开根号的区间最大值和最小值相等的话相当于区间减操作.当最大值和最小值相差1时,如果最大值是平方数那么也相当于区间减操作,否则就是区间覆盖. #include <iostream> #include <cstdio> #include <cmath> #include <algorithm> #define lson (id<<1) #de

[UOJ228] 基础数据结构练习题 - 线段树

考虑到一个数开根号 \(loglog\) 次后就会变成1,设某个Node的势能为 \(loglog(maxv-minv)\) ,那么一次根号操作会使得势能下降 \(1\) ,一次加操作最多增加 \(logloga\) 的势能. #include <bits/stdc++.h> using namespace std; typedef long long ll; const int N = 400005; ll sum[N],mx[N],mn[N],tag[N],src[N]; void add

《ACM/ICPC 算法训练教程》读书笔记 之 数据结构(线段树详解)

依然延续第一篇读书笔记,这一篇是基于<ACM/ICPC 算法训练教程>上关于线段树的讲解的总结和修改(这本书在线段树这里Error非常多),但是总体来说这本书关于具体算法的讲解和案例都是不错的. 线段树简介 这是一种二叉搜索树,类似于区间树,是一种描述线段的树形数据结构,也是ACMer必学的一种数据结构,主要用于查询对一段数据的处理和存储查询,对时间度的优化也是较为明显的,优化后的时间复杂为O(logN).此外,线段树还可以拓展为点树,ZWK线段树等等,与此类似的还有树状数组等等. 例如:要将

《数据结构》线段树入门(二)

今天继续介绍——线段树之延迟标记 接上期<数据结构>线段树入门(一):http://www.cnblogs.com/shadowland/p/5870339.html 在上期介绍了线段树的最基本内容(线段树单点修改,区间查询),这次将介绍:区间修改,区间查询. Question: 给你N个数,有两种操作: 1:给区间[a,b]的所有数增加X 2:询问区间[a,b]的数的和. 输入描述: 第一行一个正整数n,接下来n行n个整数,再接下来一个正整数Q,每行表示操作的个数,如果第一数是1,后接3个正

UOJ228 基础数据结构练习题

本文作者:ljh2000 作者博客:http://www.cnblogs.com/ljh2000-jump/转载请注明出处,侵权必究,保留最终解释权! 题目链接:http://uoj.ac/problem/228 本文版权归ljh2000和博客园共有,欢迎转载,但须保留此声明,并给出原文链接,谢谢合作. 正解:线段树 解题报告: 这道题是一道线段树的神题,需要我们支持区间开方.区间加法和区间求和操作. 关键是对于开方操作的处理,考虑如果一个区间最大值等于最小值(即全都相等),那么就可以直接开方,

【uoj228】 基础数据结构练习题

http://uoj.ac/problem/228 (题目链接) 题意 给出一个序列,维护区间加法,区间开根,区间求和 Solution 线段树.考虑区间开根怎么做.当区间的最大值与最小值相等时,我们直接对整个区间开根.最坏情况下,一次开根的复杂度最坏是${O(nlogn)}$的,然而每次开根可以迅速拉近两个数之间的大小差距,最坏复杂度的开根不会超过${5}$次. 但是考虑这样一种情况:${\sqrt{x+1}=\sqrt{x}+1}$,如果序列长成这样:${65535,65536,65535,

《数据结构》线段树入门(一)

今天介绍一种非常特殊的数据结构——线段树 首先提出一个问题: 给你n个数,有两种操作: 1:给第i个数的值增加X 2:询问区间[a,b]的总和是什么? 输入描述 输入文件第一行为一个整数n,接下来是n行n个整数,表示格子中原来的整数.接下一个正整数q,再接 下来有q行,表示q个询问,第一个整数表示询问代号,询问代号1表示增加,后面的两个数x和A表示给 位置X上的数值增加A,询问代号2表示区间求和,后面两个整数表示a和b,表示要求[a,b]之间的区间和. 样例输入 4 7 6 3 5 2 1 1

数据结构2——线段树

一.相关介绍 线段树:它在各个节点保存一条线段(数组中的一段子数组),主要用于高效解决连续区间的动态查询问题.由于二叉结构的特性,它基本能保持每个操作的复杂度为O(logn). 线段树的每个节点表示一个区间,子节点则分别表示父节点的左右半区间,例如父亲的区间是[a,b],那么(c=(a+b)/2)左儿子的区间是[a,c],右儿子的区间是[c+1,b]. 下面我们从一个经典的例子来了解线段树,问题描述如下: 从数组arr[0...n-1]中查找某个(子)数组内的最小值,其中数组大小固定,但是数组中

【数据结构】线段树 (定义 &amp; 点修改/区间查询)

[本文描述高级数据结构线段树的定义] [并解决 点修改/区间查询 的问题] 结构与定义 线段树的基本结构 ? 由图可知,线段树的每一个节点都代表着一段区间 且同一层的节点(深度相同的节点)所表示的区间互不重叠 所有叶子节点代表的区间左边界与右边界相同(叶子节点代表单个元素) 普遍规定 如果某个非叶子节点代表的区间包含元素个数为奇数 则它的左儿子包含的元素个数比右儿子大 1 在代码部分,非叶子节点表示区间为 [l,r] 则左儿子为 [ l , (l+r)/2 ] ,右儿子为 [ (l+r)/2+1