【线段树】

线段树

一、基本内容

线段树,本质是一个完全二叉树。(二叉树编号(一行一行编下来的情况):父亲节点num,则左儿子num*2右儿子num*2+1)

主要功能可以分为find(查找),update(更新)

当然,在此以前必须先build(建树)

二、模板

A : 最高分

时间限制: 1000 MS 内存限制: 131072 KB 提交总数: 106 AC总数: 53
 
问题描述
很多学校流行一种比较的习惯。老师们很喜欢询问,从某某到某某当中,分数最高的是多少。这让很多学生很反感。 不管你喜不喜欢,现在需要你做的是,就是按照老师的要求,写一个程序,模拟老师的询问。当然,老师有时候需要更新某位同学的成绩。
输入格式
第一行,有两个正整数 N 和 M ( 0<N<=200000,0<M<5000 ),分别代表学生的数目和操作的数目。 学生ID编号分别从1编到N。 第二行包含N个整数,代表这N个学生的初始成绩,其中第i个数代表ID为i的学生的成绩(范围为[1,10^9])。 接下来有M行。每一行有一个字符 C (只取'Q'或'U') ,和两个正整数A,B。 当C为'Q'的时候,表示这是一条询问操作,它询问ID从A到B(包括A,B)的学生当中,成绩最高的是多少。 当C为'U'的时候,表示这是一条更新操作,要求把ID为A的学生的成绩更改为B。
输出格式
对于每一次询问操作,在一行里面输出最高成绩。
样例输入
5 6 1 2 3 4 5 Q 1 5 U 3 6 Q 3 4 Q 4 5 U 2 9 Q 1 5
样例输出
5 6 5 9
作者
wuying
来源
OneCode
#include <cstdio>
#include <iostream>
#define maxn 200005

using namespace std;

int n, m, l, r, j;
int b[maxn];
int tr[4*maxn];
char a;

void build (int l, int r, int num)
{
    if (l == r)
    {
        tr[num] = b[l];
        return ;
    }
    int mid = (l+r)/2;
    build (l, mid, num*2);
    build (mid+1, r, num*2+1);
    tr[num] = max (tr[num*2], tr[num*2+1]);//pushup取最大值 
}//建树 

void update (int l, int r, int fi, int cg, int num)//num表示节点编号(一把用rt表示会更好),l-r表示查找的区间,fi查找的那个点的编号(不是树中的编号,是本身这个序列中的编号),cg表示要更新成为的值 
{
    if (r == fi && l == fi)
    {
        tr[num] = cg;
        return ;
    }
    int mid = (l+r)/2;
    if (fi <= mid) update (l, mid, fi, cg, num*2);
    else update (mid+1, r, fi, cg, num*2+1);
    tr[num] = max (tr[num*2], tr[num*2+1]);//pushup 
}

int find (int l, int r, int fl, int fr, int num)
{
    if (l == fl && r == fr) return tr[num];//如果找到则返回 
    int mid = (l+r)/2;//可能死循环 ?
    if(fr<=mid) return find(l,mid,fl,fr,num*2);//如果查找的区间的最右边的值都在左边那么只有往左边查下去即可 
    else if(fl>mid) return find(mid+1,r,fl,fr,num*2+1);//同理,右边 
    else return max (find (l, mid, fl, mid, num*2), find (mid+1, r, mid+1, fr, num*2+1));//两边都包含 
}

int main ()
{
    scanf ("%d%d", &n, &m);
    for (int i = 1; i <= n; i++)
    {
        scanf ("%d", &b[i]);
    }
    build (1, n, 1);//先建造一棵树 
    for (int i = 1; i <= m; i++)
    {
        cin>>a>>l>>r;//cin可以避免一些scanf读入空格等的情况,此处变量名有些古怪 
        if (a == 'U') update (1, n, l, r, 1);
        if (a == 'Q') printf ("%d\n", find (1, n, l, r, 1));
    }
    return 0;
}

*pushup表示向上更新。此树每一个父亲维护的都是两个儿子的最大值,所以pushup取max即可

*maxn表示输入的数据范围。

*为什么mid要定义在函数里面?因为如果定义成为全局变量的话,它一直在变。我left子树搜索完之后就会没法搜索right子树了。

*这个代码还有一个不好之处在于,l和r在全局变量和函数里面的变量中都定义了,虽然本质上没什么问题,但是容易引起混淆。所以最好不要这么去写。

Q:那tree为什么要maxn*4呢?

A:考虑最坏的情况,最下面一层只有一个节点。此时,整个树填满时的节点数量为4*n-1,其中n为输入的数据的个数。

三、拓展技巧

【lazy标记】

在这里,将用几道例题带领大家深入理解lazy标记。

1)是什么:父亲节点已更新,而儿子节点未更新时,儿子节点在需要进行下一步更新时需要多更新的量。(即:一种记录父亲节点“欠”儿子节点的值的多少的变量。)

2)为什么:这样可以大大减少程序运行时间。一些不必要更新的子树将永远得不到更新。

3)怎么做:定义一个struct结构体,存储每个节点的lazy标记 以及 当前值number

4)其他:不知道大家有没有发现,上面的模板题,只有pushup没有pushdown呢?真奇怪!那这里便是pushdown的出场时刻了!因为有了lazy标记,必定会有要下传的情况存在。

典型例题

C : 区间和

时间限制: 2000 MS 内存限制: 131072 KB 提交总数: 137 AC总数: 47

问题描述
你有N个整数,A1,A2,...,AN。 你需要处理两种操作。 第一种操作是对给定的区间同时加上一些给定的数字。 另一个操作是求这些区间的数字总和。
输入格式
第一行包含两个数字N和Q. 1≤N,M≤100000。 第二行包含N个数字,A1,A2,...,AN的初始值。 -1000000000≤Ai≤1000000000。 接下来的M行,每行代表一个操作。 "C a b c"代表对区间[a,b]的每一个数字都加上c。 "Q a b"代表求区间[a,b]每一个数的和。
输出格式
对于每一个"Q a b",你都要输出它的结果。
样例输入
10 5 1 2 3 4 5 6 7 8 9 10 Q 4 4 Q 1 10 Q 2 4 C 3 6 3 Q 2 4
样例输出
4 55 9 15
作者
wuying
来源
OneCode
#include <cstdio>
#include <iostream>
#define maxn 100005
#define LL long long//ll 代替 long long的功能 ,此题出于数据范围的需要使用了long long 
using namespace std;

LL n, m;
LL a[maxn];
struct node{
    LL val, lz;//val值,lz标记 
}tree[maxn*4];

void pushup(LL num)
{
    tree[num].val = tree[num*2].val + tree[num*2+1].val;
}

void pushdown(LL l,LL mid,LL r,LL num)//懒标记的下传 
{
    if(tree[num].lz!=0)//如果有需要下传的 
    {
        tree[num*2].lz+=tree[num].lz;
        tree[num*2].val+=tree[num].lz*(mid-l+1);//懒标记只表示一个点,要*len 
        tree[num*2+1].lz+=tree[num].lz;
        tree[num*2+1].val+=tree[num].lz*(r-mid);//值、laz标记多维度都要下传 
        tree[num].lz=0;//下传成功,父节点即可清零 
    }
}

void build (LL l, LL r, LL num)
{
    if (l == r)
    {
        tree[num].val = a[l];
        return ;
    }
    LL mid = (l+r) / 2;
    build (l, mid, num*2);
    build (mid+1, r, num*2+1);
    pushup(num);
}

LL find (LL l, LL r, LL fl, LL fr, LL num)
{
    if (l == fl && r == fr)
    {
        //printf ("num:%d, val:%d\n", num, tree[num].val);
        return tree[num].val;
    }
    LL mid = (l + r) / 2;
    pushdown(l,mid,r,num); 
    if (fr <= mid) return find (l, mid, fl, fr, num*2);
    else if (fl > mid) return find (mid+1, r, fl, fr, num*2+1);
    else return find (l, mid, fl, mid, num*2) + find (mid+1, r, mid+1, fr, num*2+1);
}

void add (LL l, LL r, LL fl, LL fr, LL sum, LL num)
{
    if (l == fl && r == fr)
    {
        tree[num].val+=(fr-fl+1)*sum;
        tree[num].lz+=sum;//
        return ;
    }
    LL mid = (l+r)/2;
    pushdown(l,mid,r,num); 
    if (fr <= mid) add (l, mid, fl, fr, sum, num*2);
    else if (fl > mid) add (mid+1, r, fl, fr, sum, num*2+1);
    else add (l, mid, fl, mid, sum, num*2) , add (mid+1, r, mid+1, fr, sum, num*2+1);
    pushup(num);
}

int main ()
{
    scanf("%lld%lld", &n, &m);
    for (LL i = 1; i <= n; i++)
        scanf ("%lld", &a[i]);
    build (1, n, 1);
    char flag;
    LL ma, mb, ms;
    for (LL i = 1; i <= m; i++)
    {
        cin>>flag>>ma>>mb;
        if (flag == 'C') scanf ("%lld", &ms), add (1, n, ma, mb, ms, 1);
        if (flag == 'Q') printf ("%lld\n", find (1, n, ma, mb, 1));
        //printf ("\n");
    }
//    for (LL i = 1; i <= n*4; i++)
//        printf ("%d ", tree[i].val);
//    printf ("\n");
//    for (LL i = 1; i <= n*4;i++)printf ("%d ", tree[i].lz);
    return 0;
}

 

*编辑小知识:ctrl+R可以替换(即把代码中的一些长得一样的字符统统替换成另一种长得一样的字符。使用是特别注意:printf里面有个int,千万不要把它也替换出来了)
 

 例题提升1

D : 花神游历各国

时间限制: 1000 MS 内存限制: 131072 KB 提交总数: 0 AC总数: 0
问题描述
花神喜欢步行游历各国,顺便虐爆各地竞赛。花神有一条游览路线,它是线性得,也就是说,所有游历国家呈一条线得形状排列,花神对每个国家都有一个喜欢程度(当然花神并不一定喜欢所有国家)。 每一次旅行中,花神会选择一条旅游线路,它在那一串国家中是连续得一段,这次旅行带来得开心值是这些国家得喜欢度总和。 当然花神对这些国家得喜欢度并不是恒定的,有时会突然对某些国家产生反感,使他对这些国家的喜欢度delta变为√delta,也就是开根号(可能是花神虐爆了那些国家得OI,从而感到乏味)。 现在给出花神每次旅行路线,以及开心度的变化,请求出花神每次旅行的开心值。
输入格式
第一行是一个整数N,表示有N个国家。 第二行有N个空格隔开的整数,表示每个国家的初始喜欢度data[i]。 第三行是一个整数M,表示有M条信息要处理 第四行到最后,每行3个整数,x,l,r(l<=r)当x=1时询问游历国家l到r的开心值总和,也就是∑_l^r▒〖data[i]〗,当x=2表示国家l到r中每个国家的喜欢度delta=√delta, 注意:建议使用sqrt函数,且向下取整。 【数据规模】 对于100%的数据, n ≤ 100000,m≤200000 ,data[i]非负且小于10^9
输出格式
每次x=1时,每行一个整数,表示这次旅行的开心度
样例输入
4 1 100 5 5 5 1 1 2 2 1 2 1 1 2 2 2 3 1 1 4
样例输出
101 11 11
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
解题思路
这道题,会发现不断开根号,很快就会变成1,而1就不需要再开根了,因为根号1=1.只需要用lazy标记记录一整个区间是否已经都为1即可
错误思路
容易受之前的惯性思维影响,去统一处理开根。这是极其错误的,因为根号a+根号b≠根号(a+b)
#include <cstdio>
#include <iostream>
#include <cmath>
#define maxn 100005

using namespace std;

long long n, a[maxn], m, jud, l, r;

struct node{
    bool flag;
    long long num;
}tree[maxn*4];

//void print ()
//{
//    printf ("\n");
//    for (int i = 1; i <= n*4-1; i++)
//        printf ("%d %d\n", tree[i].num, tree[i].flag);
//}//调试用:输出整棵树 

void build (long long l, long long r, long long num)
{
    if (l == r)
    {
        tree[num].num = a[l];
        tree[num].flag = 0;
        return ;
    }
    long long mid = (l + r) / 2;
    build (l, mid, num*2);
    build (mid+1, r, num*2+1);//
    tree[num].num = tree[num*2].num + tree[num*2+1].num;
}

long long find (long long l, long long r, long long ll, long long rr, long long num)
{
//    cout<<l<<' '<<r<<' '<<ll<<' '<<rr<<' '<<num<<" tree[num].num="<<tree[num].num<<endl;
    if (l == ll && r == rr)
    {
        return tree[num].num;
    }
    long long mid = (l+r) / 2;
    if (ll > mid) return find (mid+1, r, ll, rr, num*2+1);
    else if (rr <= mid) return find (l, mid, ll, rr, num*2);
    else return find (l, mid, ll, mid, num*2) + find (mid+1, r, mid+1, rr, num*2+1);
}

void update (long long l, long long r, long long ll, long long rr, long long num)
{
    if (tree[num].flag == 1) return ;
    if (tree[num].num == 1||tree[num].num == 0)//不可以r-l+1,因为会有0的结点存在 ,此处是单点flag的更新 
    {
        tree[num].flag = 1;
        return ;
    }
//    cout<<l<<' '<<r<<' '<<ll<<' '<<rr<<' '<<num<<" tree[num].num="<<tree[num].num<<endl;
    if (l==r)//注意此处是l==r而不是ll == rr!!!要查找的范围只有1个点,不代表区间已经缩进到了一个点!!! 
    {
        tree[num].num = floor (sqrt (tree[num].num));
        return ;
    }
    long long mid = (l+r) / 2;
    if (ll > mid) update (mid+1, r, ll, rr, num*2+1);
    else if (rr <= mid) update (l, mid, ll, rr, num*2);
    else update (l, mid, ll, mid, num*2), update (mid+1, r, mid+1, rr, num*2+1);
    tree[num].num = tree[num*2].num + tree[num*2+1].num;//pushup
    if (tree[num*2].flag == 1 && tree[num*2+1].flag == 1)//此处是整个区间flag的更新 
        tree[num].flag = 1;
}

int main ()
{
    scanf ("%lld", &n);
    for (int i = 1; i <= n; i++)
        scanf ("%lld", &a[i]);
    build (1, n, 1);
    scanf ("%lld", &m);
    for (int i = 1; i <= m; i++)
    {
        scanf ("%lld%lld%lld", &jud, &l, &r);
        if (jud == 1) printf ("%lld\n", find (1, n, l, r, 1));
        else update (1, n, l, r, 1);
    }
    return 0;
}

 

 *特别提醒1:正如注视中所述,千万不可以sum==r-l+1就判断整个区间全都是1了,因为会有0的情况存在!!!

*特别提醒2:注意开根号的条件是l==r而不是ll == rr!!!要查找的范围只有1个点,不代表区间已经缩进到了一个点!!! !

总结:做此类题目,一定只能去小范围地修改代码。代码整体结构不要变,特别是update和find这两个必用的函数,最好一点也不要动,以免一时多打出现刑辱mid打成r之类的低级错误。

例题提升2

E : 维护序列

时间限制: 2000 MS 内存限制: 131072 KB 提交总数: 0 AC总数: 0
问题描述
JYY 有一个维护数列的任务。他希望你能够来帮助他完成。 JYY 现在有一个长度为 N 的序列 a1,a2,…,aN,有如下三种操作: 1、 把数列中的一段数全部乘以一个值; 2、 把数列中的一段数全部加上一个值; 3、 询问序列中的一段数的和。 由于答案可能很大,对于每个询问,你只需要告诉 JYY 这个询问的答案对 P取模的结果即可。
输入格式
第一行包含两个正整数, N 和 P; 第二行包含 N 个非负整数,从左到右依次为 a1,a2,…,aN。 第三行有一个整数 M,表示操作总数。 接下来 M 行,每行满足如下三种形式之一: 1、“ 1 t g c”(不含引号)。表示把所有满足 t ≤ i ≤ g 的 ai 全部乘以 c; 2、“ 2 t g c”(不含引号)。表示把所有满足 t ≤ i ≤ g 的 ai 全部加上 c; 3、“ 3 t g”(不含引号)。表示询问满足 t ≤ i ≤ g 的 ai 的和对 P 取模的值。 1 ≤ N,M ≤ 10^5, 1 ≤ P, c, ai ≤ 2*10^9, 1 ≤ t ≤ g ≤ N
输出格式
对于每个以 3 开头的操作,依次输出一行,包含对应的结果。
样例输入
7 43 1 2 3 4 5 6 7 5 1 2 5 5 3 2 4 2 3 7 9 3 1 3 3 4 7
样例输出
2 35 8 【样例解释】 初始时数列为(1,2,3,4,5,6,7)。 经过第 1 次操作后,数列为(1,10,15,20,25,6,7)。 对第 2 次操作,和为 10+15+20=45,模 43 的结果是 2。 经过第 3 次操作后,数列为(1,10,24,29,34,15,16} 对第 4 次操作,和为 1+10+24=35,模 43 的结果是 35。 对第 5 次操作,和为 29+34+15+16=94,模 43 的结果是 8。
 
解题思路
这题就比较复杂了,是一道好题目。
由于题目要同时做两种运算,所以一个lazy标记就会出现自相矛盾等等的尴尬混乱场面。那如果建两棵树呢?(这是我曾经有过的一种想法)那么,交换赋值对方更新的数据的时间将很长很长。而且看数据范围,就知道是不可能实现的啦。
这道题目的难点在于,我给你的+*顺序是打乱的,如果lazy积压到一定量之后,分不清哪个*先来的还是哪个+先来的,lazy也就瘫痪了。
所以,我们大胆滴设想:是不是可以有两个lazy标记呢!
这也是本题目的重难点:lazy1,加法,用来记录父亲欠儿子的每个节点的应当加的量。所以,sum儿子+=len*lazy1(len是这段区间的长度)
lazy2,乘法,用来记录父亲欠儿子的每个节点的应当乘的量。那每个节点乘n,其实总和也是*n的。所以,sum儿子*=n
上面的写法不知是否容易引起误解:其实每个sum操作上面都单独拿出来了。如果合在一起,应是这样的:
sum儿子 = sum儿子*n + len*lazy1
那么,如何避免上面所说的重难点问题呢?
我们只要在addtion,multiple函数当中都对lazy1进行更新即可:加就是正常的加;乘的话,也是正常的乘。也就是说,将本身的乘法提前在lazy1这个加标记中乘入,那么之后的更新就只需要对儿子进行“加”的处理即可。也就是上面标红的公式的操作。
#重中之重#
   因为标记是要一层层传递下去的,所以我们还应当考虑到孙子的感受。儿子的laz表不是直接继承(+=)父亲的laz就可以的。这道题目由于加乗混合的特殊性,儿子的laz还要乘上父亲传承下来的应当乘的数值,再由上面的红色公式可见,应当先乘后加,因为要加的值前面已经经历过乘法了。
//说明:此题注释掉的大片内容基本上都是原先的错误代码。 
#pragma GCC optimize(3)//O3优化,正式考试禁用,不用管。 
#include <cstdio>
#include <iostream>
#define maxn 100005
#define ll long long

using namespace std;

ll n, mod, m;//mod即为p
ll a[maxn];
ll l, r;
ll sum;//需要去加上或者乘以的数字
ll flag;

struct node{
    ll num, laz, laz2=1;//laz加,laz2乘 ,初始值要为1 
}tree[maxn*4];

void build (ll l, ll r, ll num)
{
    tree[num].laz2=1;
    tree[num].laz=0;
    if (l == r)
    {
        tree[num].num = a[l];
        return ;
    }
    ll mid = (l+r) / 2;
    build (l, mid, num*2);
    build (mid+1, r, num*2+1);
    tree[num].num = (tree[num*2].num + tree[num*2+1].num) % mod;//此处必须mod,不然在下面down函数中laz2是int范围而num略大于int,则long long也会爆掉。而且这种错误很不容易被检查出来,所以一定要勤快mod,想不清的一概mod 
}

void down (ll num, ll mid, ll l, ll r)
{
    tree[num*2].num = ((tree[num*2].num*tree[num].laz2) % mod+tree[num].laz*(mid-l+1)) % mod; 
    tree[num*2+1].num = ((tree[num*2+1].num*tree[num].laz2) % mod+tree[num].laz*(r-mid)) % mod;//前面红色公式的实现 
    tree[num*2].laz= ((tree[num*2].laz*tree[num].laz2)%mod+tree[num].laz)%mod;
    tree[num*2+1].laz= ((tree[num*2+1].laz*tree[num].laz2)%mod+tree[num].laz)%mod;//重点:因为标记是要一层层传递下去的,所以我们还应当考虑到孙子的感受。儿子的laz表不是直接继承(+=)父亲的laz就可以的。这道题目由于加乗混合的特殊性,儿子的laz还要乘上父亲传承下来的应当乘的数值,再由上面的红色公式可见,应当先乘后加,因为要加的值前面已经经历过乘法了。 
    tree[num].laz = 0;
    tree[num*2].laz2*= tree[num].laz2;
    tree[num*2+1].laz2*= tree[num].laz2;
    tree[num].laz2 = 1;
    tree[num*2].laz2%=mod;
    tree[num*2+1].laz2%=mod;//核心1 
//    tree[num*2].laz+= tree[num].laz;    //    +
//    tree[num*2].laz2*= tree[num].laz2;    //    *
//    tree[num*2].laz%=mod;
//    tree[num*2].laz2%=mod;
//    tree[num*2+1].laz+= tree[num].laz;
//    tree[num*2+1].laz2*= tree[num].laz2;
//    tree[num*2+1].laz%=mod;
//    tree[num*2+1].laz2%=mod;
//    tree[num*2].num = (tree[num*2].num+tree[num].laz*(mid-l+1))*tree[num].laz2;
//    tree[num*2].num%=mod;
//    tree[num*2+1].num = (tree[num*2+1].num+tree[num].laz*(mid-l+1))*tree[num].laz2;
//    tree[num*2+1].num%=mod;
//    tree[num].laz2 = 1;
//    tree[num].laz = 0;//原标记已经下传,需要清零
}

//void down2 (ll num, ll mid, ll l, ll r)
//{
//    tree[num*2].laz2*=tree[num].laz2;
//    tree[num*2].laz2%=mod;
//    tree[num*2+1].laz2*=tree[num].laz2;
//    tree[num*2+1].laz2%=mod;
//    tree[num*2].num = (tree[num*2].num+tree[num].laz)*(mid-l+1);
//    tree[num*2].num%=mod;
//    tree[num*2+1].num = (tree[num*2+1].num+tree[num].laz)*(mid-l+1);
//    tree[num*2+1].num%=mod;
//}

void jia (ll l, ll r, ll fl, ll fr, ll num)//addtion
{
    if (fl == l && fr == r)
    {
        tree[num].num = (tree[num].num + (sum * (r-l+1))% mod) % mod;//总和的加,不忘*len 
        tree[num].laz = (tree[num].laz + sum) % mod;//加标记加 
//        tree[num].num = ((tree[num].num%mod+(r-l+1)*sum%mod)%mod*(tree[num].laz2%mod))%mod;
//        tree[num].num = ((tree[num].num%mod+(r-l+1)*sum%mod))%mod;
//        tree[num].laz = tree[num].laz*tree[num].laz2+sum;
//        tree[num].laz%=mod;
//        tree[num].laz2 = 1;
        return ;
    }//核心2 
    ll mid = (l+r) /2;
    down (num, mid, l, r);
    if (fr <= mid) jia (l, mid, fl ,fr, num*2);
    else if (fl > mid) jia (mid+1, r, fl, fr, num*2+1);
    else jia (l, mid, fl, mid, num*2), jia (mid+1, r, mid+1, fr, num*2+1);
    tree[num].num = (tree[num*2].num + tree[num*2+1].num) % mod;
}

void cheng (ll l, ll r, ll fl, ll fr, ll num)//multiple
{
    if (fl == l && fr == r)
    {
        tree[num].num = (tree[num].num * sum) % mod;
        tree[num].laz2 = (tree[num].laz2 * sum) % mod;
        tree[num].laz = (tree[num].laz * sum) % mod;//加标记乘,以便向儿子“还债”:将本身要成的转化成要加的 
//        tree[num].laz2*=sum;
//        tree[num].laz2%=mod;
//        tree[num].num = (tree[num].num%mod * (sum%mod))%mod;
        return ;
    }//核心3 
    ll mid = (l+r) / 2;
    down (num, mid, l, r);
    if (fr <= mid) cheng (l, mid, fl, fr, num*2);
    else if (fl > mid) cheng (mid+1, r, fl, fr, num*2+1);
    else cheng (l, mid, fl, mid, num*2), cheng (mid+1, r, mid+1, fr, num*2+1);
    tree[num].num = (tree[num*2].num + tree[num*2+1].num) % mod;
}

ll find (ll l, ll r, ll fl, ll fr, ll num)
{
    if (fl == l && fr == r)
    {
        return tree[num].num;
    }
    ll mid = (l + r) / 2;
    down (num, mid, l, r);
    if (fr <= mid) return find (l, mid, fl, fr, num*2);//左边
    else if (fl > mid) return find (mid+1, r, fl, fr, num*2+1);//右边
    else return (find (l, mid, fl, mid, num*2) + find (mid+1, r, mid+1, fr, num*2+1))%mod;//两边都有
}

void read(ll &x)//读入挂,考试时可以使用 
{
    x=0;
    int 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();}
    x=x*f;
}

int main ()
{
    read(n);read(mod);
    for (ll i = 1; i <= n; i++) read(a[i]);
    build (1, n, 1);
    read(m);
    for (int i = 1; i <= m; i++)
    {
        read(flag);
        read(l);read(r);
        if (flag == 2) scanf ("%lld", &sum), jia (1, n, l, r, 1);
        if (flag == 1) scanf ("%lld", &sum), cheng (1, n, l, r, 1);//审题,1 2的顺序不要弄反了 
        if (flag == 3) printf ("%lld\n", find (1, n, l, r, 1)%mod);
    }
    return 0;
}

 特别提醒

1mod:尽量勤快,即使开了long long而数据范围只有int的情况下,也要勤于mod,想不清楚就都mod.
例如本题注释。
 
四、学习心得
  线段树,我学了一个多月吧。虽然到现在还不是很熟练,代码中也常常会出现一些低级错误,但是我能看到自己的成长。算法总是越学越难的。新学会一种算法,不求快只求稳;不求算法学习之多,只求已学算法掌握踏踏实实。这也便是写博客的最佳理由吧。以后还是要多打题,积累手感经验。

 

转载于:https://www.cnblogs.com/sun31415shine/p/10858662.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值