第一部分:树状数组,又称二叉索引树(动态连续和查询问题)
树状数组的常规操作包括两种:
1. Add(x, d):让Ax增加d;
2.Query(L,R): 计算Al +…+Ar;
以下是示例题目和代码:
士兵杀敌(二)
时间限制: 1000 ms | 内存限制: 65535 KB难度: 5描述
南将军手下有N个士兵,分别编号1到N,这些士兵的杀敌数都是已知的。
小工是南将军手下的军师,南将军经常想知道第m号到第n号士兵的总杀敌数,请你帮助小工来回答南将军吧。
南将军的某次询问之后士兵i可能又杀敌q人,之后南将军再询问的时候,需要考虑到新增的杀敌数。
输入
- 只有一组测试数据
- 第一行是两个整数N,M,其中N表示士兵的个数(1<N<1000000),M表示指令的条数。(1<M<100000)
- 随后的一行是N个整数,ai表示第i号士兵杀敌数目。(0<=ai<=100)
- 随后的M行每行是一条指令,这条指令包含了一个字符串和两个整数,首先是一个字符串,如果是字符串QUERY则表示南将军进行了查询操作,后面的两个整数m,n,表示查询的起始与终止士兵编号;如果是字符串ADD则后面跟的两个整数I,A(1<=I<=N,1<=A<=100),表示第I个士兵新增杀敌数为A.
输出
- 对于每次查询,输出一个整数R表示第m号士兵到第n号士兵的总杀敌数,每组输出占一行
样例输入
5 6
1 2 3 4 5
QUERY 1 3
ADD 1 2
QUERY 1 3
ADD 2 3
QUERY 1 2
QUERY 1 5
样例输出
6
8
8
20
解题代码:
#include<iostream> #define N 1000005 using namespace std; int a[N],n,m; string mod1 = "QUERY",mod2 = "ADD"; inline int lb(int n) { return n & (-n); } int set(int n,int k) { while(n <= m) { a[n] += k; n += lb(n); } return 0; } int sum(int n) { int tot = 0; while(n != 0) { tot += a[n]; n -= lb(n); } return tot; } int main() { int x,y; string mod; cin>>n>>m; for(int i = 1; i <= n; i++) { cin>>x; set(i, x); } for(int i = 1; i <= m ; i++) { cin>>mod>>x>>y; if(mod == mod1) cout<<sum(y) - sum(x-1)<<endl; else set(x, y); } return 0; }
以上的代码基本可以当做模板来使用了,至于原理这里不再赘述。
第二部分:RMQ问题(静态范围最小值问题)
操作只有一种:计算 min{Ai, Ai+1, Ai+2,…Aj};
最常用的是ST算法,预处理时间为 O(n*logn),查询时间为 O(1),常数非常小。
预处理操作:令 d(i, j) 表示从i开始,长度为2^j的一段元素中的最小值,则可以用递推的方式计算 d(i,j) :d(i, j) = min{ d(i, j-1), d(i+2^(j-1), j-1)} (2^j < n)
代码如下:查询操作很简单,代码如下:void RMQ_init(int a[],int n) //n为数组a的长度 { for(int i - 0; i < n; i++) d[i][0] = a[i]; for(int j = 1; (1<<j) <= n; j++) for(int k = 0; k + (1<<j) -1 < n; k++) d[k][j] = min(d[k][j-1], d[k+(1<<(j-1))][j-1]); }
int RMQ(int L,int R) { int k = 0; while((1<<(k+1)) <= R-L+1) k++; return min(d[L][K], d[R-(1<<k)+1][k]); }
第三部分:线段树(动态范围最小值问题)
建树过程:
struct line { int left,right;//左端点、右端点 int n;//记录这条线段出现了多少次,默认为0 }; struct line a[100]; int sum; //建立 void build(int s,int t,int n) { int mid = (s+t)/2; a[n].left = s; a[n].right = t; if (s==t) return; a[n].left = s; a[n].right = t; build(s, mid, 2*n); build(mid+1, t, 2*n+1); }
第一部分:线段树:点修改
点修改线段树的操作包括两种:
1.Update(x, y): 把Ax修改为 v。2.Query(L, R) : 计算min(Al, Al+1, …,Ar) 。
修改过程:
int minv[N]; int p,v; //修改:a[p] = v; //本题中使用 minv[o] 表示 对应节点中的最小值 void update(int o, int l,int r) { int m = l + (l+r)/2; if(l == r) minv[o] = v; else //l < r { //先递归更新左子树或者右子树 if(p <= m) update(o*2, l, m); else update(o*2+1, m+1, r); minv[o] = min(minv[o*2], minv[o*2+1]); } }
查询过程:
int ql,qr; //查询【ql, qr】中的最小值 int minv[N] = {0}; //本题中使用 minv[o] 表示 对应节点中的最小值 int query(int o, int l,int r) { int m = l + (l+r)/2; int ans = 0X7FFFFFFF; if(ql <= l && qr >= r) return minv[o]; //当前节点完全包含在查询区间内。 if(ql <= m) ans = min(ans, query(o*2, l, m)); if(m < qr) ans = max(ans, query(o*2+1, m+1, r)); return ans; }