♥可持久化线段树(函数式线段树):
可持久化数据结构(Persistent data structure)就是利用函数式编程的思想使其支持询问历史版本、同时充分利用它们之间的共同数据来减少时间和空间消耗。
所以这里讲的可持久化线段树也叫函数式线段树(又叫主席树……因为先驱就是fotile主席Orz……)。偶还是比较喜欢叫它函数式线段树。
两篇论文:
《范浩强_wc2012谈谈各种数据结构》 ---范浩强
《可持久化数据结构研究》 ---陈立杰
OOOOOrrrzz…………
两篇博客:
http://seter.is-programmer.com/posts/31907.html 很详细的介绍了函数式线段树(主席树)。
http://fotile.is-programmer.com/posts/31250.html 主席亲笔啊 Orz……
反正大体的思想就是只赋值不修改,保存历史版本------每次加入新结点后都返回一颗包含新结点的新树保存起来(什么?这么多树空间消耗太太太大?------充分利用历史版本)
可用函数式线段树做的题目:POJ2104; SPOJ COT,COT2,COT4; BZOJ 1901(ZJU 2112); BZOJ2653; HDOJ 4417; HDOJ4348。
♣POJ 2104 K-th Number (HDU 2665)(不带修改的区间第k小值)
首先来看 线段树找第k小:假设数是在(0,n)之间的,对权值建一颗线段树,每个节点用cnt表示这个权值上的数的个数。那么考虑在当前节点t内找t节点内的第k小。如果t的左儿子上数的个数cnt已经大于k,那么第k小一定在左儿子中,所以就在左儿子中找第k小数。否则,答案就是在右儿子中找第k-cnt(leftson)小的数。
现在来考虑区间第k小。直接用上面的方法显然是不行的,因为可能区间和节点会有交叉(区间一部分占一个节点的一部分,另一部分占另一个节点的一部分)。
预备知识:两颗结构相同的权值线段树a,b,权值线段树a(+/-)b的每个节点的cnt值就是对应权值线段树a的cnt值减去b的cnt值。那么对于权值线段树a-b,我们想对它查询并不需要建出它,只需要在a、b中对应的位置分别维护即可。
开始求区间第k小:先对所有数离散化处理,然后用ati表示对a0,a1,...,ai-1按上面说的方法建权值线段树。那么ati可以通过ati-1添加一个位置得到。那么得到所有的ati只需要O(nlogn)的时间和空间。那么要询问区间al+1,al+2,...,ar的第k小数,只需要在线段树atr-atl上找第k小数就可以了。算法复杂度O(nlogn) +O(logn) +O(nlogn)。
1 #include <iostream> 2 #include <cstdio> 3 #include <cstdlib> 4 #include <cmath> 5 #include <vector> 6 #include <stack> 7 #include <queue> 8 #include <map> 9 #include <algorithm> 10 #include <string> 11 #include <cstring> 12 #define MID(x,y) ((x+y)>>1) 13 14 using namespace std; 15 16 const int MAXN = 100010; 17 struct tree 18 { 19 int l,r; 20 int ls,rs; 21 int sum; 22 }t[MAXN*20]; 23 int tot,root[MAXN]; 24 25 int build(int l, int r) 26 { 27 int k = ++ tot; 28 t[k].l = l, t[k].r = r; 29 t[k].sum = 0; 30 if (l == r) return k; 31 int mid = MID(l,r); 32 t[k].ls = build(l,mid); 33 t[k].rs = build(mid+1,r); 34 return k; 35 } 36 int change(int o, int x, int v) 37 { 38 int k = ++ tot; 39 t[k] = t[o]; 40 t[k].sum += v; 41 if (t[o].l == x && t[o].r == x) 42 return k; 43 int mid = MID(t[o].l, t[o].r); 44 if (x <= mid) t[k].ls = change(t[o].ls, x, v); 45 else t[k].rs = change(t[o].rs, x, v); 46 return k; 47 } 48 int query(int n,int o,int k) //询问区间[t1,t2]第k小 49 { 50 if (t[n].l == t[n].r) return t[n].l; 51 int res = t[t[n].ls].sum-t[t[o].ls].sum; 52 if (k <= res) 53 return query(t[n].ls,t[o].ls,k); 54 else return query(t[n].rs,t[o].rs,k-res); 55 } 56 57 int b[MAXN],sortb[MAXN]; 58 int q; 59 int main() 60 { 61 //freopen("test.in","r+",stdin); 62 int n,m; 63 while(scanf("%d%d",&n,&m)!=EOF) 64 { 65 for (int i = 1; i <= n; i ++) 66 scanf("%d",&b[i]),sortb[i]=b[i]; 67 sort(sortb+1, sortb+n+1); 68 int i; 69 for (q = 1, i = 2; i <= n; ++ i) 70 if (sortb[q] != sortb[i]) 71 sortb[++q] = sortb[i]; 72 root[0] = build(1,q); 73 for (int i = 1; i <= n; i ++) 74 { 75 int p = lower_bound(sortb+1, sortb+n+1, b[i])-sortb; 76 root[i] = change(root[i-1], p, 1); 77 } 78 79 for (int i = 0; i < m; i ++) 80 { 81 int a,b,k; 82 scanf("%d%d%d",&a,&b,&k); 83 printf("%d\n",sortb[query(root[b],root[a-1],k)]); 84 } 85 86 } 87 return 0; 88 }
PS: 这个可以归纳为一类问题 --> 当我们在权值线段树上可以完成某些对整个区间的操作而对子区间无力时,可以用这种函数式线段树的转换方法来完成,但需要满足一个条件:线段树中保存的数据要支持区间减法(就是诸如区间[l,r]可以通过区间[1,r] - [1,l-1]来算出)。我曾想过用这种函数式线段树的方法做HDU 4358,结果写完调试发现不对就是因为那题不满足区间减法。。。囧。当然我还是觉得函数式线段树是可以做那题的,只不过我对这个还不太深入掌握(说白了就是能力捉鸡T_T。。。)
(未完待续...)