首页 > 代码库 > [填坑]主席树
[填坑]主席树
首先,可持久化数据结构,CLJ的论文里有讲。
通俗点来讲,就是该数据结构保留历史版本信息,对应的有可持久化链表,可持久化线段树,可持久化树状数组。比如对于线段树更新操作,每次更新新建一棵线段树,那么就有任意一次时间点信息。
当然,这样就非常耗空间,所以,对于线段树,每次只需要对于有更新的节点新建节点,否者可以用前一个版本的顶点(就是连接一下左右孩子就行了),这样,如果每次都是单点更改的话每次也就更改logn个点,空间复杂度nlogn。
接下来我们讲讲什么是主席树(orz 主席)。
如果我的理解没错的话(有错轻拍),主席树是一种特殊的线段树,特殊在于它具有前缀性质。也就是说,他对于每个i建立一颗线段树,维护1..i里面数字出现的信息,这样的话他又满足减法性,适合一些特殊问题(比如求区间第K大等经典问题)。
Poj2104
静态查询区间第K大
直接把出现的数字离散化,离散完建立线段树,接下去,对于每个1..i建立一个线段树,查询了(l, r),那么就是T[r]-T[l-1]的结果(T[i]表示第I棵线段树)
具体看代码:
1 /* 2 * Author: Yzcstc 3 * Created Time: 2014/7/8 13:24:35 4 * File Name: poj2104.cpp 5 */ 6 #include<cstdio> 7 #include<iostream> 8 #include<cstring> 9 #include<cstdlib> 10 #include<cmath> 11 #include<algorithm> 12 #include<string> 13 #include<map> 14 #include<set> 15 #include<vector> 16 #include<queue> 17 #include<stack> 18 #include<ctime> 19 #define rep(i, a, b) for (int i = (a); i <= (b); ++i) 20 #define red(i, a, b) for (int i = (a); i >= (b); --i) 21 #define M0(x) memset(x, 0, sizeof(x)) 22 #define MP make_pair 23 #define PB push_back 24 #define eps 1e-8 25 #define pi acos(-1.0) 26 #define N 101000 27 #define M (35 * N) 28 typedef long long LL; 29 using namespace std; 30 int lson[M], rson[M], c[M]; 31 int a[N], n, m, q, tot, T[N]; 32 vector<int> v; 33 34 void init(){ 35 tot = 0; 36 v.clear(); 37 for (int i = 1; i <= n; ++i) 38 scanf("%d", &a[i]), v.push_back(a[i]); 39 sort(v.begin(), v.end()); 40 v.erase(unique(v.begin(), v.end()), v.end()); 41 m = v.size(); 42 } 43 44 int hash(const int x){ 45 return lower_bound(v.begin(), v.end(), x) - v.begin() + 1; 46 } 47 48 int build(int l, int r){ 49 int root = tot++; 50 c[root] = 0; 51 if (l < r){ 52 int mid = (l + r) >> 1; 53 build(l , mid); 54 build(mid + 1, r); 55 } 56 return root; 57 } 58 59 int updata(int root, int p, int val){ // 60 int newroot = tot++, tmp = newroot; 61 int l = 1, r = m; 62 while (1){ 63 c[newroot] = c[root] + val; 64 if (l == r) break; 65 int mid = (l + r) >> 1; 66 if (p <= mid){ 67 lson[newroot] = tot++; rson[newroot] = rson[root]; //位于左子树,左子树更新节点,右子树用原来节点 68 newroot = lson[newroot]; root = lson[root]; 69 r = mid; 70 } 71 else 72 { 73 lson[newroot] = lson[root]; rson[newroot] = tot++; //位于右子树 74 newroot = rson[newroot]; root = rson[root]; 75 l = mid + 1; 76 } 77 } 78 return tmp; 79 } 80 81 int query(int lroot, int rroot, int k){ 82 int ret = 0, l = 1, r = m; 83 while (1){ 84 if (l == r) return v[l - 1]; 85 int mid = (l + r) >> 1; 86 if (c[lson[rroot]] - c[lson[lroot]] >= k){ //左子树已经至少包含K个 87 rroot = lson[rroot]; 88 lroot = lson[lroot]; 89 r = mid; 90 } 91 else 92 { 93 k -= (c[lson[rroot]] - c[lson[lroot]]); //答案维护右子树 94 rroot = rson[rroot]; 95 lroot = rson[lroot]; 96 l = mid + 1; 97 } 98 } 99 return l;100 }101 102 void solve(){103 T[0] = build(1, m);104 for (int i = 1; i <= n; ++i){105 int p = hash(a[i]);106 T[i] = updata(T[i-1], p, 1);107 }108 int l, r, k;109 for (int i = 0; i < q; ++i){110 scanf("%d%d%d", &l, &r, &k);111 printf("%d\n", query(T[l-1], T[r], k));112 }113 114 }115 116 int main(){117 freopen("a.in", "r", stdin);118 freopen("a.out", "w", stdout);119 while (scanf("%d%d", &n, &q) != EOF){120 init();121 solve();122 }123 fclose(stdin); fclose(stdout);124 return 0;125 }
hdu4417
更上面差不多的题目,只不过查询改成了给定l,r,h,查询l,r间多少个不大于h。
找到原数组中第一个<=h的数的下标P,直接查询P前面有多少个即可。
1 /* 2 * Author: Yzcstc 3 * Created Time: 2014/7/8 15:45:55 4 * File Name: hdu4417.cpp 5 */ 6 #include<cstdio> 7 #include<iostream> 8 #include<cstring> 9 #include<cstdlib> 10 #include<cmath> 11 #include<algorithm> 12 #include<string> 13 #include<map> 14 #include<set> 15 #include<vector> 16 #include<queue> 17 #include<stack> 18 #include<ctime> 19 #define rep(i, a, b) for (int i = (a); i <= (b); ++i) 20 #define red(i, a, b) for (int i = (a); i >= (b); --i) 21 #define M0(x) memset(x, 0, sizeof(x)) 22 #define MP make_pair 23 #define PB push_back 24 #define eps 1e-8 25 #define pi acos(-1.0) 26 #define N 101000 27 #define M (N * 35) 28 typedef long long LL; 29 using namespace std; 30 int a[N], t[N], n, m, q, cas; 31 int c[M], lson[M], rson[M], tot, T[N]; 32 33 int build(const int l,const int r){ 34 int root = tot++; 35 c[root] = 0; 36 if (l < r){ 37 int mid = (l + r) >> 1; 38 build(l, mid); 39 build(mid + 1, r); 40 } 41 return root; 42 } 43 44 void init(){ 45 tot = 0; 46 scanf("%d%d", &n, &q); 47 for (int i = 1; i <= n; ++i) 48 scanf("%d", &a[i]), t[i] = a[i]; 49 sort(t + 1, t + 1 + n); 50 m = unique(t + 1, t + 1 + n) - t - 1; 51 } 52 53 int hash(const int x){ 54 return lower_bound(t + 1, t + m + 1, x) - t; 55 } 56 57 int updata(int root,const int p,const int val){ 58 int newroot = tot++, tmp = newroot; 59 int l = 1, r = m; 60 while (1){ 61 c[newroot] = c[root] + val; 62 if (l == r) break; 63 int mid = (l + r) >> 1; 64 if (p <= mid){ 65 lson[newroot] = tot++; rson[newroot] = rson[root]; 66 newroot = lson[newroot]; root = lson[root]; 67 r = mid; 68 } 69 else 70 { 71 lson[newroot] = lson[root]; rson[newroot] = tot++; 72 newroot = rson[newroot]; root = rson[root]; 73 l = mid + 1; 74 } 75 } 76 return tmp; 77 } 78 79 int query(int lroot, int rroot,const int p){ 80 if (!p) return 0; 81 int ret = 0, l = 1, r = m; 82 while (1){ 83 if (l == r) break; 84 int mid = (l + r) >> 1; 85 if (p <= mid){ 86 lroot = lson[lroot]; 87 rroot = lson[rroot]; 88 r = mid; 89 } 90 else 91 { 92 ret += (c[lson[rroot]] - c[lson[lroot]]); 93 lroot = rson[lroot]; 94 rroot = rson[rroot]; 95 l = mid + 1; 96 } 97 } 98 return ret + c[rroot] - c[lroot]; 99 }100 101 void solve(){102 T[0] = build(1, m);103 for (int i = 1; i <= n; ++i){104 int p = hash(a[i]);105 T[i] = updata(T[i-1], p, 1); 106 }107 for (int i = 0; i < q; ++i){108 int l, r, h;109 scanf("%d%d%d", &l, &r, &h);110 l++, r++;111 int p = hash(h);112 while (p > 0 && t[p] > h) --p;113 printf("%d\n", query(T[l-1], T[r], p)); 114 }115 }116 117 int main(){118 // freopen("a.in", "r", stdin);119 // freopen("a.out", "w", stdout);120 scanf("%d", &cas);121 for (int i = 1; i <= cas; ++i){122 printf("Case %d:\n", i);123 init();124 solve();125 }126 fclose(stdin); fclose(stdout);127 return 0;128 }
hdu4348
更定n个数,然后4个操作:
1. C l r d: [l, r]区间都增加d,并且时间点+1
2. Q l r:查询当前[l,r]的和
3. H l r t: 查询t时间点时[l,r]的和
4. B t:数组返回到t时间的状态
可以用离线算法或者在线算法。、,主要说说在线算法吧。。
1)可以用采用可持久化树状数组,具体的话采用这位大牛博客的方法(区间修改,区间和的查询),然后对于每个时间点维护弄一个树状数组,
遇到Back操作就把T以上的时间点出栈。。
2)采用可持久化线段树,但是区间更改直接更改到某一小段(对于[l,r],由顶至下的所有第一个满足[L,r] <=[l,r]才需要更改),往下就不用更改,查询时把父节点增加值加上就行
code(Bit):
1 /* 2 * Author: Yzcstc 3 * Created Time: 2014/7/9 12:59:07 4 * File Name: hdu4348_bit.cpp 5 */ 6 #include<cstdio> 7 #include<iostream> 8 #include<cstring> 9 #include<cstdlib> 10 #include<cmath> 11 #include<algorithm> 12 #include<string> 13 #include<map> 14 #include<set> 15 #include<vector> 16 #include<queue> 17 #include<stack> 18 #include<ctime> 19 #define rep(i, a, b) for (int i = (a); i <= (b); ++i) 20 #define red(i, a, b) for (int i = (a); i >= (b); --i) 21 #define M0(x) memset(x, 0, sizeof(x)) 22 #define MP make_pair 23 #define PB push_back 24 #define eps 1e-8 25 #define pi acos(-1.0) 26 #define N 101000 27 typedef long long LL; 28 using namespace std; 29 struct oo{ 30 LL d, dt; 31 int T; 32 oo(LL _d = 0, LL _dt = 0, int _T = 0):d(_d),dt(_dt), T(_T){} 33 void updata(long long d1, long long dt1, int T1){ 34 d += d1; 35 dt += dt1; 36 T = T1; 37 } 38 }; 39 vector<oo> b[N]; //b[x]记录b[x][i]记录第b[i].T个时间点的记录 40 long long a[N], sum[N]; 41 int n, m; 42 43 int lowbit(const int x){ 44 return x & (-x); 45 } 46 47 void updata(int x,const long long v,const int T){ 48 long long vt = v * x; 49 for (; x <= n; x += lowbit(x)){ 50 int sz = b[x].size(); 51 b[x].PB(b[x][sz-1]); //需要更改的点新建,否则信息都跟b[x][sz-1]一样(T不一样,但是不影响) 52 sz++; 53 b[x][sz-1].updata(v, vt, T); 54 } 55 } 56 57 long long query(int x,const int T){ 58 long long sd = 0, sdt = 0; 59 int cur_x = x; 60 for (; x; x -= lowbit(x)){ 61 int sz = b[x].size(); 62 while (sz && b[x][sz-1].T > T) --sz; //你会疑惑可能b[x][sz-1]最后的时间点不是T(因为前面新增时没更改到) 63 sd += b[x][sz-1].d; //那么就对了,没更改到前一个节点信息跟现在的一模一样,直接用不就得了何必更改。。 64 sdt += b[x][sz-1].dt; 65 } 66 return (cur_x + 1) * sd - sdt; // 67 } 68 69 long long query(const int l,const int r,const int T){ 70 return sum[r] - sum[l-1] + query(r, T) - query(l-1, T); 71 } 72 73 void goback(int T){ 74 for (int i = 1; i <= n; ++i){ 75 int sz = b[i].size(); 76 while (sz && b[i][sz-1].T > T) b[i].pop_back(), sz--; 77 } 78 } 79 80 void init(){ 81 for (int i = 1; i <= n; ++i){ 82 b[i].clear(); 83 b[i].PB(oo(0LL, 0LL, 0)); 84 } 85 for (int i = 1; i <= n; ++i) 86 scanf("%I64d", &a[i]), sum[i] = sum[i-1] + a[i]; 87 } 88 89 void solve(){ 90 char S[12]; 91 int l, r, d; 92 int now = 0; 93 for (int i = 0; i < m; ++i){ 94 scanf("%s", S); 95 if (S[0] == ‘Q‘){ 96 scanf("%d%d", &l, &r); 97 printf("%I64d\n", query(l, r, now)); 98 } 99 if (S[0] == ‘C‘){100 scanf("%d%d%d", &l, &r, &d);101 ++now;102 updata(l, d, now);103 updata(r+1, -d, now);104 }105 if (S[0] == ‘H‘){106 scanf("%d%d%d", &l, &r, &d);107 printf("%I64d\n", query(l, r, d));108 }109 if (S[0] == ‘B‘){110 scanf("%d", &l);111 now = l;112 goback(l);113 }114 }115 }116 117 int main(){118 freopen("a.in", "r", stdin);119 freopen("a.out", "w", stdout);120 int cas = 0;121 while (scanf("%d%d", &n, &m) != EOF){122 if (cas++) puts("");123 init();124 solve();125 }126 fclose(stdin); fclose(stdout);127 return 0;128 }
code(线段树版本,好像时间还少点):
1 /* 2 * Author: Yzcstc 3 * Created Time: 2014/7/9 12:59:07 4 * File Name: hdu4348_bit.cpp 5 */ 6 #include<cstdio> 7 #include<iostream> 8 #include<cstring> 9 #include<cstdlib> 10 #include<cmath> 11 #include<algorithm> 12 #include<string> 13 #include<map> 14 #include<set> 15 #include<vector> 16 #include<queue> 17 #include<stack> 18 #include<ctime> 19 #define rep(i, a, b) for (int i = (a); i <= (b); ++i) 20 #define red(i, a, b) for (int i = (a); i >= (b); --i) 21 #define M0(x) memset(x, 0, sizeof(x)) 22 #define MP make_pair 23 #define PB push_back 24 #define eps 1e-8 25 #define pi acos(-1.0) 26 #define N 110010 27 #define M 3450100 28 typedef long long LL; 29 using namespace std; 30 int lson[M], rson[M], c[M], tot, a[N], T[N], now, n, m; 31 long long sum[M]; 32 33 int build(int l, int r){ 34 int root = tot++; 35 c[root] = 0; 36 if (l == r) sum[root] = a[l]; 37 else 38 { 39 int mid = (l + r) >> 1; 40 lson[root] = build(l, mid); 41 rson[root] = build(mid + 1, r); 42 sum[root] = sum[lson[root]] + sum[rson[root]]; 43 } 44 return root; 45 } 46 47 int updata(int rt, const int L, const int R, const int l,const int r, const int v){ 48 int root = tot++; 49 c[root] = c[rt]; sum[root] = sum[rt]; 50 lson[root] = lson[rt]; rson[root] = rson[rt]; 51 if (l <= L && R <= r){ 52 c[root] += v; 53 sum[root] += (__int64)(R-L+1) * v; 54 return root; 55 } 56 int mid = (L + R) >> 1; 57 if (l <= mid) 58 lson[root] = updata(lson[rt], L, mid, l, r, v); 59 if (r > mid) 60 rson[root] = updata(rson[rt], mid+1, R, l, r, v); 61 sum[root] = sum[lson[root]] + sum[rson[root]] + c[root] * (R-L+1); 62 return root; 63 } 64 65 long long query(int rt,const int L, const int R, const int l, const int r, int v){ 66 if (l <= L && R <= r) 67 return sum[rt] + v * (R-L+1); 68 int mid = (L + R) >> 1; 69 long long ret = 0; 70 if (l <= mid) 71 ret += query(lson[rt], L, mid, l, r, v + c[rt]); 72 if (r > mid) 73 ret += query(rson[rt], mid+1, R, l, r, v + c[rt]); 74 return ret; 75 } 76 77 void goback(int time){ 78 if (now == time) return; 79 now = time; 80 tot = T[time+1]; 81 } 82 83 void init(){ 84 for (int i = 1; i <= n; ++i) 85 scanf("%d", &a[i]); 86 tot = 0; 87 T[0] = build(1, n); 88 } 89 90 void solve(){ 91 char S[12]; 92 int l, r, d; 93 now = 0; 94 for (int i = 1; i <= m; ++i){ 95 scanf("%s", S); 96 if (S[0] == ‘Q‘){ 97 scanf("%d%d", &l, &r); 98 printf("%I64d\n", query(T[now], 1, n, l, r, 0)); 99 }100 if (S[0] == ‘C‘){101 scanf("%d%d%d", &l, &r, &d);102 ++now;103 T[now] = updata(T[now-1], 1, n, l, r, d);104 }105 if (S[0] == ‘H‘){106 scanf("%d%d%d", &l, &r, &d);107 printf("%I64d\n", query(T[d], 1, n, l, r, 0));108 }109 if (S[0] == ‘B‘){110 scanf("%d", &l);111 goback(l);112 }113 }114 }115 116 int main(){117 // freopen("a.in", "r", stdin);118 // freopen("a.out", "w", stdout);119 int cas = 0;120 while (scanf("%d%d", &n, &m) != EOF){121 if (cas++) puts("");122 init();123 solve();124 }125 fclose(stdin); fclose(stdout);126 return 0;127 }
Zoj2112&&Bzoj1901
动态区间第K大
首先,如果是静态的话,那么可以直接用主席树维护。但是更改问题就来了。
不过试想一下,如果知道某个节点前面的修改情况,那么问题不就解决了吗。这个不就可以用树状数组来维护吗。。
举个栗子把:
比如原数组是: 1 5 2 3 4 6
当前修改信息是:1(位置1) -> 2, 3(位置4) ->4
那么在查询[2,4]第2大时,比如对于4,我们查询在原数组区间中为第2大,前面更改信息是少了3,多了4,一加一减为0,答案就是4了。
具体实现的话:就是对于N个线段树扔进树状数组里进行维护,也就是说树状数组的每个节点对应一棵线段树。
代码太丑就不贴了。。
当然也可以用线段树套平衡树的做法实现。。不过太麻烦不写了。。
具体点这
Spoj10628&&Bzoj2588
树上第K大:(spoj)给定u,v,k,求u到v路径上的第K大
(bzoj)给定u,v,k,求u ^ lastans到v路径上的第K大,lastans为上次询问答案
对于spoj10628,可以离线求出lca,但是bzoj只能在线。我用的是欧拉序列+st求的。。
其他的话,就判断root->u,root->v,root->lca之间做即可。传统的主席树是在线性上的。。这里改成树的即可
具体看代码吧
code:
1 /* 2 * Author: Yzcstc 3 * Created Time: 2014/7/10 16:04:29 4 * File Name: spoj10628.cpp 5 */ 6 #include<cstdio> 7 #include<iostream> 8 #include<cstring> 9 #include<cstdlib> 10 #include<cmath> 11 #include<algorithm> 12 #include<string> 13 #include<map> 14 #include<set> 15 #include<vector> 16 #include<queue> 17 #include<stack> 18 #include<ctime> 19 #define rep(i, a, b) for (int i = (a); i <= (b); ++i) 20 #define red(i, a, b) for (int i = (a); i >= (b); --i) 21 #define M0(x) memset(x, 0, sizeof(x)) 22 #define MP make_pair 23 #define PB push_back 24 #define eps 1e-8 25 #define pi acos(-1.0) 26 #define two(i) (1 << i) 27 #define N 101000 28 #define M 4100000 29 typedef long long LL; 30 using namespace std; 31 int T[N << 1], t[N << 1], a[N << 1], n, m, q; 32 int lson[M], rson[M], c[M], tot; 33 int f[N<<1][20], dep[N<<1], fv[N<<1], lv[N<<1], fa[N<<1], d[N << 1], inx; 34 vector<int> e[N << 1]; 35 36 void dfs(int u, int depth){ 37 dep[u] = depth; 38 fv[u] = lv[u] = ++inx; 39 d[inx] = u; 40 for (int i = 0; i < e[u].size(); ++i){ 41 int v = e[u][i]; 42 if (dep[v]) continue; 43 fa[v] = u; 44 dfs(v, depth + 1); 45 d[lv[u] = ++inx] = u; 46 } 47 48 } 49 50 void initrmq(){ //求出st的dp数组 51 M0(fa); 52 M0(dep); 53 dfs(1, 1); 54 M0(f); 55 for (int i = 1; i <= inx; ++i) 56 f[i][0] = d[i]; 57 for (int j = 1; two(j) <= inx; ++j) 58 for (int i = 1; i + two(j) - 1 <= inx; ++i) 59 f[i][j] = (dep[f[i][j-1]] < dep[f[i + two(j-1)][j-1]]) ? f[i][j-1] : f[i+two(j-1)][j-1]; 60 } 61 62 void init(){ 63 tot = inx = 0; 64 M0(a); 65 for (int i = 0; i <= n; ++i) e[i].clear(); 66 for (int i = 1; i <= n; ++i) 67 scanf("%d", &a[i]), t[i-1] = a[i]; 68 for (int i = 1; i < n; ++i){ 69 int u, v; 70 scanf("%d%d", &u, &v); 71 e[u].PB(v); 72 e[v].PB(u); 73 } 74 sort(t, t + n); 75 m = unique(t, t + n) - t; 76 initrmq(); 77 } 78 79 int queryrmq(const int L,const int R){ //st查询最小值 80 int l = min(fv[L], fv[R]); 81 int r = max(lv[L], lv[R]); 82 int k = (int)(log(r-l+1.0) / log(2.0)); 83 return dep[f[l][k]] < dep[f[r-two(k)+1][k]] ? f[l][k] : f[r-two(k)+1][k]; 84 } 85 86 int hash(const int x){ 87 return lower_bound(t, t + m, x) - t; 88 } 89 90 int build(const int l,const int r){ 91 int root = tot++; 92 if (l < r){ 93 int mid = (l + r) >> 1; 94 lson[root] = build(l, mid); 95 rson[root] = build(mid+1, r); 96 } 97 return root; 98 } 99 100 int updata(int rt, const int p, const int v){101 int root = tot++, tmp = root;102 int l = 0, r = m - 1;103 while (1){104 c[root] = c[rt] + v;105 if (l == r) break;106 int mid = (l + r) >> 1;107 if (p <= mid){108 lson[root] = tot++; rson[root] = rson[rt];109 root = lson[root]; rt = lson[rt];110 r = mid;111 }112 else113 {114 lson[root] = lson[rt]; rson[root] = tot++;115 root = rson[root]; rt = rson[rt];116 l = mid + 1;117 }118 }119 return tmp;120 }121 122 int query(int rtu, int rtv, int rtf, int fp, int k){123 int l = 0, r = m - 1;124 while (l < r){125 int mid = (l + r) >> 1;126 int tmp = c[lson[rtu]] + c[lson[rtv]] - 2 * c[lson[rtf]] + (fp <= mid && fp >= l); //多减了lca的答案,所以要加上去 127 if (tmp >= k){128 rtu = lson[rtu];129 rtv = lson[rtv];130 rtf = lson[rtf];131 r = mid;132 }133 else134 {135 rtu = rson[rtu];136 rtv = rson[rtv];137 rtf = rson[rtf];138 k -= tmp;139 l = mid + 1; 140 }141 }142 return l;143 }144 145 void dfs(const int u){ //树结构上建主席树 146 T[u] = updata(T[fa[u]], hash(a[u]), 1);147 for (int i = 0; i < e[u].size(); ++i){148 int v = e[u][i];149 if (v == fa[u]) continue;150 dfs(v);151 }152 }153 154 void solve(){155 int u, v, k, fuv;156 T[0] = build(0, m-1);157 dfs(1);158 for (int i = 0; i < q; ++i){159 scanf("%d%d%d", &u, &v, &k);160 fuv = queryrmq(u, v);161 printf("%d\n", t[query(T[u], T[v], T[fuv],hash(a[fuv]), k)]);162 }163 }164 165 int main(){166 // freopen("a.in", "r", stdin);167 // freopen("a.out", "w", stdout);168 while (scanf("%d%d", &n, &q) != EOF){169 init();170 solve();171 }172 fclose(stdin); fclose(stdout);173 return 0;174 }