主席树
主席树:可持久化线段树,可以支持查询历史版本的线段树。
在模板题中,对数组的每一个前缀建立一棵权值线段树,线段树的每一个节点维护属于区间\([l,r]\)的数的个数。
对于询问\([l_i,r_i]\)中的第\(k_i\)大的数,利用前缀的思想,版本\(r_i\)与\(l_{i-1}\)之间的差就是\([l_i,r_i]\)的信息。左儿子中数的个数为\(sum_l\),如果\(sum_l \ge k_i\),继续查询左儿子中第\(k_i\)大的数,如果\(sum_l<k_i\),查询右儿子中第\(k_i-sum_l\)大的数。
如果对于每个版本都建立一棵线段树,会MLE。但两个相邻版本的线段树,对一个节点,只会改变一个儿子,另一个儿子没有改变。没有改变的儿子用上一个版本的儿子,发生的儿子新建一个节点,所以要动态开点。
权值线段树维护的是\([l,r]\)中数的个数,所以要排序之后离散化,重要的是大小,不是数值。查询的结果是第\(k\)大的数在原数组是第几大的
查找和更新的时间复杂度为\(O(logn)\).
空树的空间复杂度为\(O(nlogn)\),更新的空间复杂度为\(O(logn)\) ,所以内存池至少开\(32n\)
代码:
点击查看代码
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn = 2E5+5;
typedef long long ll;
inline int read(){//读入优化
char ch; int flag = 1;
while((ch=getchar())<'0'||ch>'9')
if(ch == '-') flag = -1;
int res = ch-48;
while((ch=getchar())>='0'&&ch<='9')
res = res*10+ch-48;
return res*flag;
}
int n, q, m, cnt = 0;
int a[maxn], b[maxn], root[maxn];
int sum[maxn<<5], ls[maxn<<5], rs[maxn<<5];
inline int build(int l, int r){
int rt = ++cnt;
sum[rt] = 0;
if(l == r)
return rt;
int mid = (l+r) >> 1;
ls[rt] = build(l, mid);
rs[rt] = build(mid+1, r);
return rt;
}
inline int update(int pre, int l, int r, int x){
int rt = ++cnt;
ls[rt] = ls[pre], rs[rt] = rs[pre], sum[rt] = sum[pre]+1;
if(l == r)
return rt;
int mid = (l+r) >> 1;
if(x <= mid)
ls[rt] = update(ls[pre], l, mid, x);
else
rs[rt] = update(rs[pre], mid+1, r, x);
return rt;
}
inline int query(int x, int y, int l, int r, int k){
if(l == r)
return l;
int mid = (l+r) >> 1;
int tmp = sum[ls[y]] - sum[ls[x]];
if(tmp >= k)
return query(ls[x], ls[y], l, mid, k);
else
return query(rs[x], rs[y], mid+1, r, k-tmp);
}
int main(){
n = read(), q = read();
for(int i = 1; i <= n; i++)
a[i] = b[i] = read();
sort(b+1, b+1+n);
m = unique(b+1, b+1+n)-(b+1);
for(int i = 1; i <= n; i++){
int s = lower_bound(b+1, b+1+m, a[i])-b;
root[i] = update(root[i-1], 1, m, s);
}
int x, y, k;
for(int i = 1; i <= q; i++){
x = read(), y = read(), k = read();
int t = query(root[x-1], root[y], 1, m, k);
cout << b[t] << endl;
}
return 0;
}
主席树维护的是前缀区间信息。设根节点是root,版本rt[i]维护的是root~i路径上的题。则u到v路径上的信息在\(rt[u]+rt[v]-rt[lca]-rt[fa[lca]]\)中,然后在树上查询第\(k\)小.
在树剖dfs的时候顺便建立线段树,数据离散化
代码:
点击查看代码
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn = 2e5+10;
int fa[maxn], siz[maxn], top[maxn], son[maxn], dep[maxn];
struct edge{
int to, nxt;
}e[maxn<<1];
int head[maxn], tot;
int sum[maxn<<5], ls[maxn<<5], rs[maxn<<5], root[maxn];
int a[maxn], b[maxn];
int n, m, q, cnt, ans;
void add(int u, int v){
e[++tot].nxt = head[u];
e[tot].to = v;
head[u] = tot;
}
int update(int pre, int l, int r, int x){
int rt = ++cnt;
sum[rt] = sum[pre] + 1;
if(l == r)
return rt;
int mid = (l+r) >> 1;
if(x <= mid){
ls[rt] = update(ls[pre], l, mid, x);
rs[rt] = rs[pre];
}
else{
rs[rt] = update(rs[pre], mid+1, r, x);
ls[rt] = ls[pre];
}
return rt;
}
void dfs1(int u){
siz[u] = 1;
dep[u] = dep[fa[u]] + 1;
root[u] = update(root[fa[u]], 1, m, a[u]);
for(int i = head[u]; i; i = e[i].nxt){
int v = e[i].to;
if(v == fa[u])
continue;
fa[v] = u;
dfs1(v);
siz[u] += siz[v];
if(!son[u] || siz[v] > siz[son[u]])
son[u] = v;
}
}
void dfs2(int u, int tp){
top[u] = tp;
if(!son[u])
return;
dfs2(son[u], tp);
for(int i = head[u]; i; i = e[i].nxt){
int v = e[i].to;
if(v == fa[u] || v == son[u])
continue;
dfs2(v, v);
}
}
int LCA(int x, int y){
while(top[x] != top[y]){
if(dep[top[x]] >= dep[top[y]])
x = fa[top[x]];
else
y = fa[top[y]];
}
return dep[x] >= dep[y] ? y : x;
}
int query(int ql, int qr, int lca, int lca_fa, int l ,int r, int k){
if(l == r)
return l;
int x = sum[ls[ql]] + sum[ls[qr]] - sum[ls[lca]] - sum[ls[lca_fa]];
int mid = (l+r) >> 1;
if(x >= k)
return query(ls[ql], ls[qr], ls[lca], ls[lca_fa], l, mid, k);
else
return query(rs[ql], rs[qr], rs[lca], rs[lca_fa], mid+1, r, k-x);
}
inline ll read(){//读入优化
char ch;
ll sign = 1;
while((ch=getchar())<'0'||ch>'9')
if(ch == '-')
sign = -1;
ll res = ch-48;
while((ch=getchar())>='0'&&ch<='9')
res = res*10+ch-48;
return res*sign;
}
int main(){
n = read(), q = read();
for(int i = 1; i <= n; i++){
a[i] = b[i] = read();
}
sort(b+1, b+1+n);
m = unique(b+1, b+1+n) - (b+1);
for(int i = 1; i <= n; i++)
a[i] = lower_bound(b+1, b+1+m, a[i]) - b;
for(int i = 1; i < n; i++){
int u, v;
u = read(), v = read();
add(u, v);
add(v, u);
}
dfs1(1);
dfs2(1, 1);
while(q--){
int x, y, k, lca;
x = read(), y = read(), k = read();
x ^= ans;
lca = LCA(x, y);
ans=b[query(root[x],root[y],root[lca],root[fa[lca]],1,m,k)];
cout << ans << endl;
}
return 0;
}
在静态区间中,用主席树维护前缀数组。在一棵权值线段树的单点修改的时间复杂度是\(O(logn)\),所以单次修改的复杂度为\(O(nlogn)\)。单次查询的复杂度是\(O(logn)\)
妥妥会T。单点修改和区间查询可以用树状数组来做。版本\(root[i]\)维护\([i-lowbit(i)+1,i]\)的信息。在树状数组中,单点修改需要修改\(logn\)个节点,查询时求出\(logn\)个节点的和。所以在主席树上,单点修改需要修改\(logn\)棵权值线段树,查询同理。
每次修改查询时,预处理出需要的\(logn\)棵线段树
所以单点修改和区间查询的时间复杂度为\(O(log^2n)\) ,空间复杂度为\(O(nlog^2n)\)
点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5+10;
inline ll read(){//读入优化
char ch;
ll sign = 1;
while((ch=getchar())<'0'||ch>'9')
if(ch == '-')
sign = -1;
ll res = ch-48;
while((ch=getchar())>='0'&&ch<='9')
res = res*10+ch-48;
return res*sign;
}
int n, m;
int root[maxn], ls[maxn*400], rs[maxn*400], val[maxn*400], cnt;
int a[maxn], b[maxn<<1], num;
int c[maxn], d[maxn], e[maxn];
int subt[maxn], addt[maxn], subcnt, addcnt;
inline int lowbit(int x){return x&(-x);}
void update(int &rt, int l, int r, int pos, int c){
if(!rt)
rt = ++cnt;
val[rt] += c;
if(l == r)
return ;
int mid = (l+r) >> 1;
if(pos <= mid)
update(ls[rt], l, mid, pos, c);
else
update(rs[rt], mid+1, r, pos, c);
}
void modify(int pos, int val){
int x = lower_bound(b+1, b+1+num, a[pos]) - b;
for(int i = pos; i <= n; i += lowbit(i))
update(root[i], 1, num, x, val);
}
int query(int l, int r, int k){
if(l == r)
return l;
int sum = 0;;
int mid = (l+r) >> 1;
for(int i = 1; i <= addcnt; i++)
sum += val[ls[addt[i]]];
for(int i = 1; i <= subcnt; i++)
sum -= val[ls[subt[i]]];
if(sum >= k){
for(int i = 1; i <= subcnt; i++)
subt[i] = ls[subt[i]];
for(int i = 1; i <= addcnt; i++)
addt[i] = ls[addt[i]];
return query(l, mid, k);
}
else{
for(int i = 1; i <= subcnt; i++)
subt[i] = rs[subt[i]];
for(int i = 1; i <= addcnt; i++)
addt[i] = rs[addt[i]];
return query(mid+1, r, k-sum);
}
}
int query_kth(int l, int r, int k){
subcnt = addcnt = 0;
for(int i = l-1; i >= 1; i -= lowbit(i))
subt[++subcnt] = root[i];
for(int i = r; i >= 1; i -= lowbit(i))
addt[++addcnt] = root[i];
return query(1, num, k);
}
int main(){
n = read(), m = read();
for(int i = 1; i <= n; i++){
a[i] = b[++num] = read();
}
for(int i = 1; i <= m; i++){
char ch = getchar();
while(ch!='Q'&&ch!='C')
ch = getchar();
if(ch == 'Q'){
c[i] = read(), d[i] = read(), e[i] = read();
}
else{
c[i] = read(), d[i] = read();
b[++num] = d[i];
}
}
sort(b+1, b+1+num);
num = unique(b+1, b+1+num)-b-1;
for(int i = 1; i <= n; i++){
modify(i, 1);
}
for(int i = 1; i <= m; i++){
if(e[i])
printf("%d\n", b[query_kth(c[i], d[i], e[i])]);
else{
modify(c[i], -1);
a[c[i]] = d[i];
modify(c[i], 1);
}
}
return 0;
}

浙公网安备 33010602011771号