线段树
一、基本内容
线段树,本质是一个完全二叉树。(二叉树编号(一行一行编下来的情况):父亲节点num,则左儿子num*2右儿子num*2+1)
主要功能可以分为find(查找),update(更新)
当然,在此以前必须先build(建树)
二、模板
A : 最高分
#include#include #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
#include#include #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; }
例题提升1
D : 花神游历各国
#include#include #include #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< 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< 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 : 维护序列
//说明:此题注释掉的大片内容基本上都是原先的错误代码。 #pragma GCC optimize(3)//O3优化,正式考试禁用,不用管。 #include#include #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; }
特别提醒