线段树学习总结
本篇博客会持续跟新。。。。。
线段树
1.概念
线段树是一种完全二叉树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间分别对应着线段树中的一个叶节点。主要用于区间的动态查询问题,每次操作的复杂度为O(logn)。
2.性质
如果父亲节点表示区间[a,b],那么左儿子表示的区间为[a,(a + b) / 2],右儿子[(a + b) / 2 + 1,b]。
3.用途
线段树适用于和区间统计有关的问题,比如某些数据可以按照区间进行划分,按区间动态进行修改,而且还需要按区间多次进行查询,那么使用线段树可以达到较快的查询速度。
线段树的构造
因为线段树是完全二叉树,我们可以用数组表示 Tree[MAXN]表示,那么MAXN的范围应该是多少呢?如果array[N]有N个元素,看下面代码:
int dfs(int n) { if (n == 1) return 3; else { return 1 + dfs(n >> 1) + dfs(n - (n >> 1)); } }
所以,MAXN = 4 * N - 1;
int Tree[MAXN * 4 + 10]; // 存贮最小的值得下标 int Array[MAXN]; void Bulid(int node,int left,int right) { if (left == right) Tree[node] = left; else { Bulid(node << 1,left,(left + right) >> 1); Bulid(node << 1 | 1,((left + right) >> 1) + 1,right); Tree[node] = Array[Tree[node << 1]] <= Array[Tree[node << 1 | 1]] ? Tree[node << 1]:Tree[node << 1 | 1]; } } int main() { Array[0] = 1, Array[1] = 2,Array[2] = 2, Array[3] = 4, Array[4] = 1, Array[5] = 3; Bulid(1,0,5); REP_1(i,4 * 5) { cout << Tree[i] << endl; } }
线段树的区间查询
// [L,R]为要查询的区间 int Query(int L,int R,int node,int left,int right) { if (R < left || L > right) return -1; if (L <= left && right <= R) return Tree[node]; int min1 = Query(L,R,node << 1,left,(left + right) >> 1); int min2 = Query(L,R,node << 1 | 1,((left + right) >> 1) + 1,right); if (min1 == -1) return min2; if (min2 == -1) return min1; return min(min1,min2); }
线段树的单节点更新
void Updata(int node,int left,int right,int pos,int value) { if (left == right) { Array[Tree[node]] = value; return; } int mid = (left + right) >> 1; if (pos <= mid) Updata(node << 1,left,mid,pos,value); else Updata(node << 1 | 1,mid + 1,right,pos,value); Tree[node] = Array[Tree[node << 1]] <= Array[Tree[node << 1 | 1]] ? Tree[node << 1]:Tree[node << 1 | 1]; }
线段树的区间更新后面更新。。。。。。。
杭电习题陪练线段树
线段树的单点更新和求区间最大值
#include <map> #include <set> #include <list> #include <stack> #include <queue> #include <cmath> #include <ctime> #include <cstdio> #include <vector> #include <sstream> #include <cstdlib> #include <complex> #include <cstring> #include <iostream> #include <algorithm> #define REP(i,N) for (int i = 0;i < (N);i++) #define REP_1(i,N) for (int i = 1;i < (N);i++) #define REP_2(i,be,en) for (int i = (be);i < (en);i++) #define DWN(i,N) for (int i = (N);i >= 0;i--) #define DWN_1(i,N) for (int i = (N);i >= 1;i--) #define DWN_2(i,en,be) for (int i = (en);i >= (be);i--) #define FR(N) freopen((N),"r",stdin) #define FW(N) freopen((N),"w",stdout) #define GETS(ch) fgets((ch),MAXN,stdin) #define INF 0x3f3f3f3f #define MAXN 200000 * 4 + 10 #define MOD 1000000007 using namespace std; typedef long long LL; typedef map<char,int> MINT; typedef vector<int> VINT; typedef set<char> SINT; int Array[MAXN]; void Build(int node,int left,int right) { if (left == right) { scanf("%d",&Array[node]); } else { Build(node << 1,left,(right + left) >> 1); Build(node << 1 | 1,((left + right) >> 1) + 1,right); Array[node] = max(Array[node << 1],Array[node << 1 | 1]); } } int Query(int L,int R,int node,int left,int right) { if (L <= left && right <= R) { return Array[node]; } else { int ans = 0; int mid = (left + right) >> 1; if (L <= mid) ans = max(ans,Query(L,R,node << 1,left,mid)); if (R > mid) ans = max(ans,Query(L,R,node << 1 | 1,mid + 1,right)); return ans; } } void Updata(int node,int left,int right,int pos,int value) { if (left == right) { Array[node] = value; }else { int mid = (right + left) >> 1; if (pos <= mid) Updata(node << 1,left,mid,pos,value); else Updata(node << 1 | 1,mid + 1,right,pos,value); Array[node] = max(Array[node << 1],Array[node << 1 | 1]); } } int main () { int N,M; //FR("1.txt"); while (~scanf("%d%d",&N,&M)) { Build(1,1,N); REP(i,M) { char ch[2]; int A,B; scanf("%s%d%d",&ch,&A,&B); if (ch[0] == 'Q') { cout << Query(A,B,1,1,N) << endl; } else { Updata(1,1,N,A,B); } } } }
线段树的单点更新和求区间和
#include <map> #include <set> #include <list> #include <stack> #include <queue> #include <cmath> #include <ctime> #include <cstdio> #include <vector> #include <sstream> #include <cstdlib> #include <complex> #include <cstring> #include <iostream> #include <algorithm> #define REP(i,N) for (int i = 0;i < (N);i++) #define REP_1(i,N) for (int i = 1;i < (N);i++) #define REP_2(i,be,en) for (int i = (be);i < (en);i++) #define DWN(i,N) for (int i = (N);i >= 0;i--) #define DWN_1(i,N) for (int i = (N);i >= 1;i--) #define DWN_2(i,en,be) for (int i = (en);i >= (be);i--) #define FR(N) freopen((N),"r",stdin) #define FW(N) freopen((N),"w",stdout) #define GETS(ch) fgets((ch),MAXN,stdin) #define INF 0x3f3f3f3f #define MAXN 50000 * 4 + 10 #define MOD 1000000007 using namespace std; typedef long long LL; typedef map<char,int> MINT; typedef vector<int> VINT; typedef set<char> SINT; int Array[MAXN]; void Build(int node,int left,int right) { if (left == right) { scanf("%d",&Array[node]); return; } else { Build(node << 1,left,(right + left) >> 1); Build(node << 1 | 1,((left + right) >> 1) + 1,right); Array[node] = Array[node << 1] + Array[node << 1 | 1]; } } int Query(int node,int left,int right,int L,int R) { if (L <= left && right <= R) { return Array[node]; } else { int ans = 0; int mid = (left + right) >> 1; if (L <= mid) ans += Query(node << 1,left,mid,L,R); if (R > mid) ans += Query(node << 1 | 1,mid + 1,right,L,R); return ans; } } void Updata(int node,int left,int right,int pos,int value) { if (left == right) { Array[node] += value; }else { int mid = (right + left) >> 1; if (pos <= mid) Updata(node << 1,left,mid,pos,value); else Updata(node << 1 | 1,mid + 1,right,pos,value); Array[node] = Array[node << 1] + Array[node << 1 | 1]; } } int main () { int N,M; int T; int kase = 1; //FR("1.txt"); scanf("%d",&T); while (T--) { printf("Case %d:\n",kase++); scanf("%d",&N); Build(1,1,N); while(1) { char ch[10]; int A,B; scanf("%s",ch); if (ch[0] == 'Q') { scanf("%d%d",&A,&B); cout << Query(1,1,N,A,B) << endl; } else if (ch[0] == 'A'){ scanf("%d%d",&A,&B); Updata(1,1,N,A,B); } else if (ch[0] == 'S') { scanf("%d%d",&A,&B); Updata(1,1,N,A,-B); }else break; } } }
线段树的单点更新和求区间和
#include <map> #include <set> #include <list> #include <stack> #include <queue> #include <cmath> #include <ctime> #include <cstdio> #include <vector> #include <sstream> #include <cstdlib> #include <complex> #include <cstring> #include <iostream> #include <algorithm> #define REP(i,N) for (int i = 0;i < (N);i++) #define REP_1(i,N) for (int i = 1;i < (N);i++) #define REP_2(i,be,en) for (int i = (be);i < (en);i++) #define DWN(i,N) for (int i = (N);i >= 0;i--) #define DWN_1(i,N) for (int i = (N);i >= 1;i--) #define DWN_2(i,en,be) for (int i = (en);i >= (be);i--) #define FR(N) freopen((N),"r",stdin) #define FW(N) freopen((N),"w",stdout) #define GETS(ch) fgets((ch),MAXN,stdin) #define INF 0x3f3f3f3f #define MAXN 5000 * 4 + 10 #define MOD 1000000007 using namespace std; typedef long long LL; typedef map<char,int> MINT; typedef vector<int> VINT; typedef set<char> SINT; int Array[MAXN]; void Build(int node,int left,int right) { Array[node] = 0; if (left == right) { return; } else { Build(node << 1,left,(right + left) >> 1); Build(node << 1 | 1,((left + right) >> 1) + 1,right); } } int Query(int node,int left,int right,int L,int R) { if (L <= left && right <= R) { return Array[node]; } else { int ans = 0; int mid = (left + right) >> 1; if (L <= mid) ans += Query(node << 1,left,mid,L,R); if (R > mid) ans += Query(node << 1 | 1,mid + 1,right,L,R); return ans; } } void Updata(int node,int left,int right,int pos) { if (left == right) { Array[node]++; }else { int mid = (right + left) >> 1; if (pos <= mid) Updata(node << 1,left,mid,pos); else Updata(node << 1 | 1,mid + 1,right,pos); Array[node] = Array[node << 1] + Array[node << 1 | 1]; } } int main () { int N; //FR("1.txt"); while (cin >> N) { Build(1,0,N - 1); int A[MAXN]; int sum = 0; REP(i,N) { cin >> A[i]; Updata(1,0,N - 1,A[i]); if (A[i] + 1 < N) sum += Query(1,0,N - 1,A[i] + 1,N - 1); } int ans = sum; REP(i,N) { sum += N - A[i] - A[i] - 1; ans = min(ans,sum); } cout << ans << endl; } }
线段树的单点更新和求区间最大值
#include <map> #include <set> #include <list> #include <stack> #include <queue> #include <cmath> #include <ctime> #include <cstdio> #include <vector> #include <sstream> #include <cstdlib> #include <complex> #include <cstring> #include <iostream> #include <algorithm> #define REP(i,N) for (int i = 0;i < (N);i++) #define REP_1(i,N) for (int i = 1;i < (N);i++) #define REP_2(i,be,en) for (int i = (be);i < (en);i++) #define DWN(i,N) for (int i = (N);i >= 0;i--) #define DWN_1(i,N) for (int i = (N);i >= 1;i--) #define DWN_2(i,en,be) for (int i = (en);i >= (be);i--) #define FR(N) freopen((N),"r",stdin) #define FW(N) freopen((N),"w",stdout) #define GETS(ch) fgets((ch),MAXN,stdin) #define INF 0x3f3f3f3f #define MAXN 200000 * 4 + 10 #define MOD 1000000007 using namespace std; typedef long long LL; typedef map<char,int> MINT; typedef vector<int> VINT; typedef set<char> SINT; typedef pair<int,int> PINT; int Array[MAXN]; int H,W,N; void Build(int node,int left,int right) { Array[node] = W; if (left == right) { return; } else { Build(node << 1,left,(right + left) >> 1); Build(node << 1 | 1,((left + right) >> 1) + 1,right); } } int Query(int node,int left,int right,int x) { if (left == right) { Array[node] -= x; return left; } else { int mid = (left + right) >> 1; int ans; if (Array[node << 1] >= x) { ans = Query(node << 1,left,mid,x); } else ans = Query(node << 1 | 1,mid + 1,right,x); Array[node] = max(Array[node << 1],Array[node << 1 | 1]); return ans; } } int main () { //FR("1.txt"); while (~scanf("%d%d%d",&H,&W,&N)) { H = min(H,N); Build(1,1,H); while (N--){ int x; scanf("%d",&x); if (Array[1] < x) { cout << -1 << endl; } else { cout << Query(1,1,H,x) << endl; } } } }
线段树的区间更新
需要用到延迟标记,每个节点新增加一个标记,记录这个节点是否被进行了某种修改操作(会影响到子孙节点)。我们先按照查询的方式将其划分成线段树中的节点,然后修改这些节点的信息,并给这些节点标上标记,在修改和查询的时候,如果我们到了一个节点p,并且决定考虑其子节点,那么我们就要看看节点p有没有标记,如果有,就要按照标记修改其子节点的信息,并且给子节点都标上相同的标记,同时消掉p的标记。模板:
LL add[MAXN << 2]; LL sum[MAXN << 2]; void PushDown(int node,int num) { if (add[node]) { add[node << 1] += add[node]; add[node << 1 | 1] += add[node]; sum[node << 1] += add[node] * (num - (num >> 1)); sum[node << 1 | 1] += add[node] * (num >> 1); add[node] = 0; } } void Build(int node,int left,int right) { add[node] = 0; if (left == right) { scanf("%I64d",&sum[node]); return; } else { int mid = (left + right) >> 1; Build(node << 1,left,mid); Build(node << 1 | 1, mid + 1,right); sum[node] = sum[node << 1] + sum[node << 1 | 1]; } } void Updata(int node,int left,int right,int L,int R,int value) { if (L <= left && right <= R) { add[node] += value; sum[node] += (LL)value * (right - left + 1); return; } else { PushDown(node,right - left + 1); int mid = (left + right) >> 1; if (L <= mid) Updata(node << 1,left,mid,L,R,value); if (R > mid) Updata(node << 1 | 1,mid + 1,right,L,R,value); sum[node] = sum[node << 1] + sum[node << 1 | 1]; } } LL Query(int node,int left,int right,int L,int R) { if (L <= left && right <= R) { return sum[node]; } PushDown(node,right - left + 1); int mid = (left + right) >> 1; LL ans = 0; if (L <= mid) ans += Query(node << 1,left,mid,L,R); if (mid < R) ans += Query(node << 1 | 1,mid + 1,right,L,R); return ans; }
线段树的区间修改和区间求和
#include <map> #include <set> #include <list> #include <stack> #include <queue> #include <cmath> #include <ctime> #include <cstdio> #include <vector> #include <sstream> #include <cstdlib> #include <complex> #include <cstring> #include <iostream> #include <algorithm> #define REP(i,N) for (int i = 0;i < (N);i++) #define REP_1(i,N) for (int i = 1;i < (N);i++) #define REP_2(i,be,en) for (int i = (be);i < (en);i++) #define DWN(i,N) for (int i = (N);i >= 0;i--) #define DWN_1(i,N) for (int i = (N);i >= 1;i--) #define DWN_2(i,en,be) for (int i = (en);i >= (be);i--) #define FR(N) freopen((N),"r",stdin) #define FW(N) freopen((N),"w",stdout) #define GETS(ch) fgets((ch),MAXN,stdin) #define INF 0x3f3f3f3f #define MAXN 100010 #define MOD 1000000007 using namespace std; typedef long long LL; typedef map<char,int> MINT; typedef vector<int> VINT; typedef set<char> SINT; typedef pair<int,int> PINT; LL add[MAXN << 2]; LL sum[MAXN << 2]; void PushDown(int node,int num) { if (add[node]) { add[node << 1] = add[node]; add[node << 1 | 1] = add[node]; sum[node << 1] = add[node] * (num - (num >> 1)); sum[node << 1 | 1] = add[node] * (num >> 1); add[node] = 0; } } void Build(int node,int left,int right) { add[node] = 0; sum[node] = 1; if (left == right) { return; } else { int mid = (left + right) >> 1; Build(node << 1,left,mid); Build(node << 1 | 1, mid + 1,right); sum[node] = sum[node << 1] + sum[node << 1 | 1]; } } void Updata(int node,int left,int right,int L,int R,int value) { if (L <= left && right <= R) { add[node] = value; sum[node] = (LL)value * (right - left + 1); return; } else { PushDown(node,right - left + 1); int mid = (left + right) >> 1; if (L <= mid) Updata(node << 1,left,mid,L,R,value); if (R > mid) Updata(node << 1 | 1,mid + 1,right,L,R,value); sum[node] = sum[node << 1] + sum[node << 1 | 1]; } } LL Query(int node,int left,int right,int L,int R) { if (L <= left && right <= R) { return sum[node]; } PushDown(node,right - left + 1); int mid = (left + right) >> 1; LL ans = 0; if (L <= mid) ans += Query(node << 1,left,mid,L,R); if (mid < R) ans += Query(node << 1 | 1,mid + 1,right,L,R); return ans; } int main () { int T,N,M; //FR("1.txt"); cin >> T; int cas = 1; while (T--) { scanf("%d%d",&N,&M); Build(1,1,N); REP(i,M) { int a,b,c; scanf("%d%d%d",&a,&b,&c); Updata(1,1,N,a,b,c); } printf("Case %d: The total value of the hook is %I64d.\n",cas++ , sum[1]); } }
2.A Simple Problem with Integers
线段树的区间修改和区间求和
#include <map> #include <set> #include <list> #include <stack> #include <queue> #include <cmath> #include <ctime> #include <cstdio> #include <vector> #include <sstream> #include <cstdlib> #include <complex> #include <cstring> #include <iostream> #include <algorithm> #define REP(i,N) for (int i = 0;i < (N);i++) #define REP_1(i,N) for (int i = 1;i < (N);i++) #define REP_2(i,be,en) for (int i = (be);i < (en);i++) #define DWN(i,N) for (int i = (N);i >= 0;i--) #define DWN_1(i,N) for (int i = (N);i >= 1;i--) #define DWN_2(i,en,be) for (int i = (en);i >= (be);i--) #define FR(N) freopen((N),"r",stdin) #define FW(N) freopen((N),"w",stdout) #define GETS(ch) fgets((ch),MAXN,stdin) #define INF 0x3f3f3f3f #define MAXN 100010 #define MOD 1000000007 using namespace std; typedef long long LL; typedef map<char,int> MINT; typedef vector<int> VINT; typedef set<char> SINT; typedef pair<int,int> PINT; LL add[MAXN << 2]; LL sum[MAXN << 2]; void PushDown(int node,int num) { if (add[node]) { add[node << 1] += add[node]; add[node << 1 | 1] += add[node]; sum[node << 1] += add[node] * (num - (num >> 1)); sum[node << 1 | 1] += add[node] * (num >> 1); add[node] = 0; } } void Build(int node,int left,int right) { add[node] = 0; if (left == right) { scanf("%I64d",&sum[node]); return; } else { int mid = (left + right) >> 1; Build(node << 1,left,mid); Build(node << 1 | 1, mid + 1,right); sum[node] = sum[node << 1] + sum[node << 1 | 1]; } } void Updata(int node,int left,int right,int L,int R,int value) { if (L <= left && right <= R) { add[node] += value; sum[node] += (LL)value * (right - left + 1); return; } else { PushDown(node,right - left + 1); int mid = (left + right) >> 1; if (L <= mid) Updata(node << 1,left,mid,L,R,value); if (R > mid) Updata(node << 1 | 1,mid + 1,right,L,R,value); sum[node] = sum[node << 1] + sum[node << 1 | 1]; } } LL Query(int node,int left,int right,int L,int R) { if (L <= left && right <= R) { return sum[node]; } PushDown(node,right - left + 1); int mid = (left + right) >> 1; LL ans = 0; if (L <= mid) ans += Query(node << 1,left,mid,L,R); if (mid < R) ans += Query(node << 1 | 1,mid + 1,right,L,R); return ans; } int main () { int N,Q; //FR("1.txt"); cin >> N >> Q; Build(1,1,N); while (Q--) { char ch[2]; int a,b,c; scanf("%s%d%d",ch,&a,&b); if (ch[0] == 'Q') { printf("%I64d\n",Query(1,1,N,a,b)); } else { scanf("%d",&c); Updata(1,1,N,a,b,c); } } }
题意:在墙上贴海报,海报可以互相覆盖,问最后可以看见几张海报
思路:这题数据范围很大,直接搞超时+超内存,需要离散化:
离散化简单的来说就是只取我们需要的值来用,比如说区间[1000,2000],[1990,2012] 我们用不到[-∞,999][1001,1989][1991,1999][2001,2011][2013,+∞]这些值,所以我只需要1000,1990,2000,2012就够了,将其分别映射到0,1,2,3,在于复杂度就大大的降下来了
所以离散化要保存所有需要用到的值,排序后,分别映射到1~n,这样复杂度就会小很多很多
而这题的难点在于每个数字其实表示的是一个单位长度(并非一个点),这样普通的离散化会造成许多错误(包括我以前的代码,poj这题数据奇弱)
给出下面两个简单的例子应该能体现普通离散化的缺陷:
例子一:1-10 1-4 5-10
例子二:1-10 1-4 6-10
普通离散化后都变成了[1,4][1,2][3,4]
线段2覆盖了[1,2],线段3覆盖了[3,4],那么线段1是否被完全覆盖掉了呢?
例子一是完全被覆盖掉了,而例子二没有被覆盖
为了解决这种缺陷,我们可以在排序后的数组上加些处理,比如说[1,2,6,10]
如果相邻数字间距大于1的话,在其中加上任意一个数字,比如加成[1,2,3,6,7,10],然后再做线段树就好了.
线段树功能:update:成段替换 query:简单hash
#include <map> #include <set> #include <list> #include <stack> #include <queue> #include <cmath> #include <ctime> #include <cstdio> #include <vector> #include <sstream> #include <cstdlib> #include <complex> #include <cstring> #include <iostream> #include <algorithm> #define REP(i,N) for (int i = 0;i < (N);i++) #define REP_1(i,N) for (int i = 1;i < (N);i++) #define REP_2(i,be,en) for (int i = (be);i < (en);i++) #define DWN(i,N) for (int i = (N);i >= 0;i--) #define DWN_1(i,N) for (int i = (N);i >= 1;i--) #define DWN_2(i,en,be) for (int i = (en);i >= (be);i--) #define FR(N) freopen((N),"r",stdin) #define FW(N) freopen((N),"w",stdout) #define GETS(ch) fgets((ch),MAXN,stdin) #define INF 0x3f3f3f3f #define MAXN 40010 #define MOD 1000000007 using namespace std; typedef long long LL; typedef map<char,int> MINT; typedef vector<int> VINT; typedef set<int> SINT; typedef pair<int,int> PINT; int color[MAXN << 2]; class Node { public: int be,en; }X[MAXN]; int Array[MAXN]; int Hash[MAXN]; int ans = 0; void Build(int node,int left,int right) { color[node] = -1; if (left == right) return; int mid = (left + right) >> 1; Build(node << 1,left,mid); Build(node << 1 | 1,mid + 1,right); } void PushDown(int node) { if (color[node] != -1) { color[node << 1] = color[node << 1 | 1] = color[node]; color[node] = -1; } } void Updata(int node,int left,int right,int L,int R,int value) { if (L <= left && right <= R) { color[node] = value; return; } PushDown(node); int mid = (left + right) >> 1; if (L <= mid) Updata(node << 1,left,mid,L,R,value); if (R > mid) Updata(node << 1 | 1,mid + 1,right,L,R,value); } void Query(int node,int left,int right) { if (color[node] != -1) { if (!Hash[color[node]]) { Hash[color[node]] = 1; ans++; } return; } if (left == right) return; int mid = (right + left) >> 1; Query(node << 1,left,mid); Query(node << 1 | 1,mid + 1,right); } int main () { int T; //FR("1.txt"); cin >> T; while (T--) { int N; cin >> N; int num = 0; REP(i,N) { scanf("%d %d",&X[i].be,&X[i].en); Array[num++] = X[i].be; Array[num++] = X[i].en; } sort(Array,Array + num); num = unique(Array,Array + num) - Array; DWN_2(i,num - 1,1) { if (Array[i] - Array[i - 1] != 1) { Array[num++] = Array[i - 1] + 1; } } sort(Array,Array + num); Build(1,0,num - 1); REP(i,N) { int be = lower_bound(Array,Array + num,X[i].be) - Array; int en = lower_bound(Array,Array + num,X[i].en) - Array; Updata(1,0,num - 1,be,en,i); } memset(Hash,0,sizeof(Hash)); ans = 0; Query(1,0,num - 1); cout << ans << endl; } }