线段树笔记(坑没填完但是已完结系列)
emmmmm….
昨天刚学完全线段树,今天先来记录一波….
指不定之后还需要写更多的东西….?
所以说目前为止只学习了基本操作和懒标记我也很无奈.jpg
线段树的基本操作
学习线段树的日常基础思考:
给定一个长度为n的数组,对其中某段序列进行m次如下可能操作:
- 给ai加上v
- 给区间[L , R]中的每一个数加上v
- 求区间[L , R]的最大/最小值
- 求区间[L , R]内所有数的和
- 查询ai的值
当然,这些操作都可以只用一个数组a进行模拟来完成
现在我们分析一下复杂度:对于操作1、5来说,每次的时间复杂度为O(1),因为只需要修改或输出数组a[i]的值就好了,但是对于操作2、3、4来说,每次的时间复杂度为O(区间长度)(时间复杂度计算我根本不会所以是搬来的现成数据)
在理论上的最坏情况下,所需要的时间复杂度为O(mn)
对于这样的复杂度,小于十万的数据或许还行,大于十万就非常棘手了
因此我们需要能够进行较大规模数据的数据结构。
例如:线段树。
线段树本质上是维护下标为1,2,..,n的n个按顺序排列的数的信息,所以,其实是“点树”,是维护n的点的信息,至于每个点的数据的含义可以有很多,
在对线段操作的线段树中,每个点代表一条线段,在用线段树维护数列信息的时候,每个点代表一个数,但本质上都是每个点代表一个数。以下,在讨论线段树的时候,区间[L,R]指的是下标从L到R的这(R-L+1)个数,而不是指一条连续的线段。只是有时候这些数代表实际上一条线段的统计结果而已。
线段树是将每个区间[L,R]分解成[L,M]和[M+1,R] (其中M=(L+R)/2 这里的除法是整数除法,即对结果下取整)直到 L==R 为止。
下面我们用一张图来演示线段树对于区间的划分过程
以长度为13的序列举例
划分过程如下
如上图,每一个节点都代表一段区间的信息
这就是区间树。可以证明,二叉区间树的深度为logn(本文中所有的log均为log2)
因为在这个树上,我们最终将这个区间都划分成了长度为1的区间
因此实际上我们要对点进行操作时或对区间进行操作时,只需要不断划分区间即可。
因为当我们在区间树上找到一个区间属于当前要操作的区间时,其从属的区间没有必要进行查找,那么显然在区间树上查询一个区间的值的时间复杂度是log级别的
这样的话我们就能将单点操作和区间操作的时间复杂度进行均摊,达到log级别。
因为线段树实际是以点来表示区间的,即:每一个点上所记录的实际是对应区间的信息
一下对与需要对二叉树的点进行说明时,用“区间”代替点
那么我们来口头实现一下最初要求实现的操作:
对单个节点进行修改,我们不断递归进行二分,知道枚举到对应的叶子节点,然后修改即可
对于区间修改来说,我们需要通过递归找到所有对应的区间(包括对应区间的子区间),然后进行修改
对于区间查询来说,只需要找到对应的区间即可
下面放一下代码?
我还是放具体题目然后丢大段代码吧…优秀板子我将放出某gy神的博客的链接…高端代码自取系列:http://www.cnblogs.com/hinanawitenshi/p/8093624.html
懒标记
我们先对代码进行一下分析:
对于操作:
单点修改只是修改树上的一条路径,长度最长为logn,因此复杂度为O(logn)
对于区间和最大(小)值查询,实际上还是对一条路径进行搜索,而且要用到的长度更小,就算极限,复杂度也只是O(logn)
但是对于区间修改,我们一次修改的是一个区间的所有点,最坏情况下,我们要对1—n所有的数都进行修改,实际是修改了整棵树,好像复杂度并没有得到改善,那么我们就得另想他法了:
仔细想想好像没有什么办法,因为要修改的话似乎必须遍历所有的数,但是实际上,我们为什么一定要进行修改呢,仔细想想,对于一个区间,我们除了在对他的左右儿子区间进行查询而进行划分时,会需要他的左右儿子区间,但是其余时刻我们好像并不需要用到,那么我们不妨只对这个区间对应的值进行修改,然后我们打上一个标记,表示如果需要划分这个区间,那么这个区间的左右儿子的值也需要修改。这个标记,我们叫做懒标记
那么这样的话我们是否达到了优化区间修改的作用呢?
对于区间修改来说,我们仅将原先要修改的子树的根节点打上了懒标记,然后返回,因此其时间复杂度与区间的询问是相同的,即O(logn)
对于区间询问来说,我们只是在遍历到带有标记的节点时,才对标记进行相关的处理,而关于标记的处理的复杂度显然是O(1)的,对整体的时间复杂度不造成影响,即区间询问的时间复杂度仍然是O(logn)
建树只是增加了标记的初始化,单点操作则不需要进行初始化,其时间复杂度不变
这样我们就将线段树一次修改的整体复杂度降到了O(logn)
#include <bits/stdc++.h> #define maxn 100057 using namespace std; struct tre{ int delta,maxx,sum; }tree[maxn]; int n,m; int a[maxn]; void pushdown(int pos,int l,int r){//懒标记下传 if(!tree[pos].delta)return; int lc=pos*2,rc=pos*2+1,m=(l+r)/1,vv=tree[pos].delta; tree[lc].sum+=(m-l+1)*vv,tree[rc].sum+=(r-m+1)*vv; tree[lc].maxx+=vv,tree[rc].maxx+=vv; tree[lc].delta+=vv,tree[rc].delta+=vv; tree[pos].delta=0; } void maintain(int pos){//重新计算区间的最大值和区间和 int lc=pos*2,rc=pos*2+1; tree[pos].maxx=max(tree[lc].maxx,tree[rc].maxx); tree[pos].sum=tree[lc].sum+tree[rc].sum; } void build(int pos,int l,int r){//建树 if(l==r){ tree[pos].delta=0; tree[pos].maxx=tree[pos].sum=a[l]; return; } int m=(l+r)/2; build(pos*2,l,m); build(pos*2+1,m+1,r); maintain(pos); } int query_max(int pos,int L,int R,int l,int r){//区间最大值查询 if(l>R||r<L) return 0; if(l>=L&&r<=R) return tree[pos].maxx; pushdown(pos,l,r); int m=l+r>>1; return max(query_max(pos<<1,L,R,l,m),query_max((pos<<1)+1,L,R,m+1,r)); } int query_sum(int pos,int L,int R,int l,int r){//区间和查询 if(l>R||r<L) return 0; if(l>=L&&r<=R) return tree[pos].sum; pushdown(pos,l,r); int m=l+r>>1; return query_sum(pos*2,L,R,l,m)+query_sum(pos*2+1,L, R,m+1,r); } void updata(int pos,int L,int R,int l,int r,int v){//区间修改 if(l>R||r<L) return; if(l>=L&&r<=R){//此大括号内容根据题意自定 tree[pos].delta+=v; tree[pos].maxx+=v; tree[pos].sum+=(r-l+1)*v; return; } pushdown(pos,l,r); int m=(l+r)/2; updata(pos*2,L,R,l,m,v); updata(pos*2+1,L,R,m+1,r,v); maintain(pos); } int main(){ cin>>n>>m; for(int i=1;i<=n;i++) cin>>a[i]; build(1,1,n); while(m--){ int bj,x,y,k; cin>>bj; if(bj==1){ cin>>x>>y>>k; updata(1,x,y,1,n,k); } else if(bj==2){ cin>>x>>y; cout<<query_sum(1,x,y,1,n)<<endl; } else{ cin>>x>>y cout<<query_max(1,x,y,1,n)<<endl; } } return 0; }
不要在意这丑陋的码风和莫名其妙的斜字体
关于区间奇数位和偶数位和的求解
对于一个区间的奇数位和偶数位,我们可以知道的是,区间总和减去奇数位和等于偶数位和
但是在维护时,区间有一些需要注意的问题
比如一个大区间
[1, 2, 3, 4, 5, 6]
二分后得到[1, 2, 3]和[4, 5, 6]
显然其中奇数位1,3,5.然而按照最初建树时情况来看,我们记录的是1, 3和4,6的值
那么如何得到5的值的,可以用右边区间的总和 减去所记录的奇数位和
由这个例子推广即可。
对于查询,我们要如何知道我们在查的是奇数位和还是偶数位和呢?
对于一个大区间[L, R]中,我们查到了一个小区间[l, r]
若l - L是奇数,我们可以知道的是,从L到l中,有算上l的三个数,所以l就是整个区间的偶数位
而l对于小区间[l, r]来说,是奇数位,所以对于小区间[l, r]我们就要取偶数位,否则就取奇数位
举例代码,20181025九校联考T2
1 #include<bits/stdc++.h> 2 #define ll long long 3 using namespace std; 4 const int maxn = 500010; 5 const int maxm = 1000010; 6 const int mod = 1000000007; 7 struct shiki { 8 ll sum, delta, l; 9 }tree[maxm << 3]; 10 ll c[maxn << 1]; 11 int n, m; 12 ll fac[maxn], inv[maxn]; 13 ll ans_ma = 0, ans_mi = 0; 14 15 inline ll read() { 16 ll x = 0, y = 1; 17 char ch = getchar(); 18 while(!isdigit(ch)) { 19 if(ch == '-') y = -1; 20 ch = getchar(); 21 } 22 while(isdigit(ch)) { 23 x = (x << 1) + (x << 3) + ch - '0'; 24 ch = getchar(); 25 } 26 return x * y; 27 } 28 29 inline ll power(ll a, ll b) { 30 ll res = 1; 31 for(; b; b >>= 1) { 32 if(b & 1) res = res * a % mod; 33 a = a * a % mod; 34 } 35 return res; 36 } 37 38 inline void init() { 39 fac[1] = 1, inv[1] = 1; 40 for(int i = 2; i <= maxn; ++i) { 41 fac[i] = (fac[i - 1] * i) % mod;//阶乘 42 inv[i] = power(fac[i], mod - 2) % mod;//逆元 43 } 44 } 45 46 inline ll C(int n, int m) {//n!/m!(n-m)! = n! * m!^mod-2 * (n - m)!^mod-2 47 return fac[n] * inv[m] % mod * inv[n - m] % mod;} 48 49 inline void maintain(int pos, int l, int r) { 50 int lc = pos << 1, rc = pos << 1 | 1, mid = l + r >> 1; 51 tree[pos].sum = tree[lc].sum + tree[rc].sum; 52 tree[pos].l = tree[lc].l + (((mid - l + 1) & 1) ? tree[rc].sum - tree[rc].l : tree[rc].l); 53 tree[pos].sum %= mod, tree[pos].l %= mod; 54 } 55 56 inline void pushdown(int pos, int l, int r) { 57 if(!tree[pos].delta) return; 58 int lc = pos << 1, rc = pos << 1 | 1, mid = l + r >> 1, del = tree[pos].delta; 59 tree[lc].sum += (mid - l + 1) * del, tree[lc].l += (mid - l + 2) / 2 * del; 60 tree[rc].sum += (r - mid) * del, tree[rc].l += (r - mid + 1) / 2 * del; 61 tree[lc].sum %= mod, tree[lc].l %= mod; 62 tree[rc].sum %= mod, tree[rc].l %= mod; 63 tree[lc].delta += del, tree[rc].delta += del; 64 tree[pos].delta = 0; 65 } 66 67 void build(int pos, int l, int r){ 68 if(l == r) { 69 tree[pos].sum = c[l]; 70 tree[pos].l = c[l]; 71 return; 72 } 73 int mid = l + r >> 1; 74 build(pos << 1, l, mid); 75 build(pos << 1 | 1, mid + 1, r); 76 maintain(pos, l, r); 77 } 78 79 void update(int pos, int L, int R, int l, int r, int val) { 80 if(l > R || r < L) return ; 81 if(l >= L && r <= R) { 82 tree[pos].sum += (r - l + 1) * val; 83 tree[pos].delta += val; 84 tree[pos].l += (r - l + 2) / 2 * val; 85 tree[pos].l %= mod, tree[pos].sum %= mod; 86 return; 87 } 88 if(l != r) pushdown(pos, l, r); 89 int mid = l + r >> 1; 90 update(pos << 1, L, R, l, mid, val); 91 update(pos << 1 | 1, L, R, mid + 1, r, val); 92 maintain(pos, l, r); 93 } 94 95 ll query_sum(int pos, int L, int R, int l, int r) { 96 if(l > R || r < L) return 0; 97 if(l >= L && r <= R) return tree[pos].sum % mod; 98 if(l != r) pushdown(pos, l, r); 99 int mid = l + r >> 1; 100 return (query_sum(pos << 1, L, R, l, mid) + query_sum(pos << 1 | 1, L, R, mid + 1, r)) % mod; 101 } 102 103 ll query_l(int pos, int L, int R, int l, int r) { 104 if(l > R || r < L) return 0; 105 if(l >= L && r <= R) 106 return ((l - L) & 1) ? tree[pos].sum - tree[pos].l : tree[pos].l; 107 if(l != r) pushdown(pos, l, r); 108 int mid = l + r >> 1; 109 return (query_l(pos << 1, L, R, l, mid) + query_l(pos << 1 | 1, L, R, mid + 1, r)) % mod; 110 } 111 112 int main() { 113 freopen("sort.in", "r", stdin); 114 freopen("sort.out", "w", stdout); 115 init(); 116 n = read(), m = read(); 117 for(int i = 1; i <= 2 * n; ++i) c[i] = read(); 118 // sort(c + 1, c + 2 * n + 1); 119 if(n <= 5100) { 120 for(int i = 1; i <= m; ++i) { 121 ll opt = read(), l = read(), r = read(); 122 if(l > r) swap(l, r); 123 if(opt == 0) { 124 ll val = read(); 125 for(int j = l; j <= r; ++j) 126 c[j] += val; 127 } 128 else if(opt == 1) { 129 ll ln = (r - l + 1), lm = (r - l + 1) >> 1, mid = (l + r) >> 1; 130 ll op = C(ln, lm) % mod * power(lm + 1, mod - 2) % mod; 131 ans_ma = 0, ans_mi = 0; 132 for(int j = l; j <= mid; ++j) ans_ma -= c[j]; 133 for(int j = mid + 1; j <= r; ++j) ans_ma += c[j]; 134 for(int j = l; j <= r; j += 2) ans_mi -= c[j]; 135 for(int j = l + 1; j <= r; j += 2) ans_mi += c[j]; 136 printf("%lld %lld %lld\n", ans_ma, ans_mi, op); 137 } 138 } 139 return 0; 140 } 141 else { 142 n = 2 * n; 143 build(1, 1, n); 144 for(int i = 1; i <= m; ++i) { 145 ll opt = read(), l = read(), r = read(); 146 if(l > r) swap(l, r); 147 if(opt == 0) { 148 ll val = read(); 149 update(1, l, r, 1, n, val); 150 } 151 else if(opt == 1) { 152 ll ln = (r - l + 1), lm = (r - l + 1) >> 1, mid = (l + r) >> 1; 153 ll op = C(ln, lm) % mod * power(lm + 1, mod - 2) % mod; 154 ans_ma = (query_sum(1, mid + 1, r, 1, n) - query_sum(1, l, mid, 1, n) + mod) % mod; 155 ans_mi = (query_l(1, l + 1, r, 1, n) - query_l(1, l, r, 1, n) + mod) % mod; 156 printf("%lld %lld %lld\n", ans_ma, ans_mi, op); 157 } 158 } 159 } 160 return 0; 161 }