主席树学习笔记

主席树是一种极其强大的数据结构。

记录了一颗线段树经过若干次修改的历史版本。

可以用来水各种题

原理

首先,主席树是线段树的历史版本。

即可持久化线段树。

就是每次操作后的线段树的节点信息。

这些节点信息若每次操作后都保留下来。

对空间和时间的消耗都是巨大的。

因此,我们需要可持久化线段树,主席树来储存。

我们知道,线段树的左右儿子的编号是该节点编号的两倍和两倍加一。

结构如下图所示:

image

对于一个已知的叶子节点,与他有关的节点只有\(logn\)个,即树高。

因此,每次修改后,只有\(logn\)个节点发生了改变。

那我们为什么要把所有的节点都复制一遍呢?

我们可以共有不会改变的节点,以减少空间以及时间开销。

那么,我们该如何共用节点同时又改变信息呢。

我们只用新建一些会发生改变的信息的节点,并把记录该次操作后的根节点。

image

如图所示,蓝色节点为原有节点,而红色节点为新建节点,结合红色节点和蓝色节点我们可以求出完整为线段树。

由图可见,主席树的左右儿子不再是原本线段树的左右儿子了。

所以,我们在新建一个节点的同时,要继承上一次操作的节点的所有信息,然后在修改当前的信息。

同时,对于一般的线段树,我们节点维护的信息一般都是序列的值(非权值)。

而线段树要维护历史版本的信息,因此,我们不能利用普通的线段树维护信息的方式去维护。

我们去维护的是一颗权值线段树由于主席树是权值线段树,我们可以直接在树上二分,减掉一个\(log\)的时间复杂度。),即线段树上维护的是权值的个数。

这样,线段树就可以加减了。。。

满足了可加减后,我们可以干很多事情。

维护区间\([L,R]\)可以转化为两个前缀和相减。

大部分的题都可以解决了。

题目

HDU2665

说到序列上的操作不得不提起经典的区间第\(k\)值。

题目中给出了一些区间,要求求出这些区间的第\(k\)值。

先来回忆一下,一个序列的第\(k\)值是怎么求的。

我们可以二分我们要求的第\(k\)值,我们只要\(check\)即可。

我们该如何去\(check\)呢?既然他是第\(k\)值,那小于等于他的数就有\(k\),按照这个二分即可。

同时主席树是权值线段树,我们可以直接在主席树上二分时间复杂度\(O(logn)\),完美解决。

查询区间第\(k\)值函数如下:

int query(int pre,int nxt,int L,int R,int k) {
    if(L==R)return L;
    int mid=(L+R)>>1;
    int Sum=tot[Ls[nxt]]-tot[Ls[pre]];//左儿子区间节点数量
    if(Sum<k)return query(Rs[pre],Rs[nxt],mid+1,R,k-Sum);//递归到右儿子
    else return query(Ls[pre],Ls[nxt],L,mid,k);//递归到左儿子
}

代码如下

#include <cstdio>
#include <cstring>
#include <vector>
#include <iostream>
#include <algorithm>
 
using namespace std;
 
#define LL long long
#define reg register
#define clr(a,b) memset(a,b,sizeof a)
#define Mod(x) (x>=mod)&&(x-=mod)
#define debug(x) cerr<<#x<<"="<<x<<endl;
#define debug2(x,y) cerr<<#x<<"="<<x<<" "<<#y<<"="<<y<<endl;
#define debug3(x,y,z) cerr<<#x<<"="<<x<<" "<<#y<<"="<<y<<" "<<#z<<"="<<z<<endl;
#define rep(a,b,c) for(reg int a=(b),a##_end_=(c); a<=a##_end_; ++a)
#define ret(a,b,c) for(reg int a=(b),a##_end_=(c); a<a##_end_; ++a)
#define drep(a,b,c) for(reg int a=(b),a##_end_=(c); a>=a##_end_; --a)
#define erep(i,G,x) for(int i=(G).Head[x]; i; i=(G).Nxt[i])
 
inline int Read(void) {
    int res=0,f=1;
    char c;
    while(c=getchar(),c<48||c>57)if(c=='-')f=0;
    do res=(res<<3)+(res<<1)+(c^48);
    while(c=getchar(),c>=48&&c<=57);
    return f?res:-res;
}
 
template<class T>inline bool Min(T &a, T const&b) {
    return a>b?a=b,1:0;
}
template<class T>inline bool Max(T &a, T const&b) {
    return a<b?a=b,1:0;
}
const int N=1e5+5,M=2e5+5,mod=1e9+7;
 
bool MOP1;
 
struct Function_SEG {
    int cnt,Ls[N*20],Rs[N*20],tot[N*20];
    void Insert(int &Now,int pre,int L,int R,int x) {
        Now=++cnt;
        Ls[Now]=Ls[pre];
        Rs[Now]=Rs[pre];
        tot[Now]=tot[pre]+1;
        if(L==R)return;
        int mid=(L+R)>>1;
        if(x<=mid)Insert(Ls[Now],Ls[pre],L,mid,x);
        else Insert(Rs[Now],Rs[pre],mid+1,R,x);
        tot[Now]=tot[Ls[Now]]+tot[Rs[Now]];
    }
    int query(int pre,int nxt,int L,int R,int k) {
        if(L==R)return L;
        int mid=(L+R)>>1;
        int Sum=tot[Ls[nxt]]-tot[Ls[pre]];
        if(Sum<k)return query(Rs[pre],Rs[nxt],mid+1,R,k-Sum);
        else return query(Ls[pre],Ls[nxt],L,mid,k);
    }
} tr;
 
int A[N],B[N],root[N];
 
bool MOP2;
 
void _main(void) {
    int n=Read(),m=Read();
    rep(i,1,n)A[i]=B[i]=Read();
    sort(B+1,B+n+1);
    int len=unique(B+1,B+n+1)-B-1;
    rep(i,1,n) {
        A[i]=lower_bound(B+1,B+len+1,A[i])-B;
        tr.Insert(root[i],root[i-1],1,len,A[i]);
    }
    while(m--) {
        int L=Read(),R=Read(),k=R-L+2-Read();
        int Now=tr.query(root[L-1],root[R],1,len,k);
        printf("%d\n",B[Now]);
    }
}
 
signed main() {
    _main();
    return 0;
}

HDU3333

我们需要求出区间不重复元素和。

我们来回忆一下离线的做法,利用树状数组维护所有的排序好的区间。

所以对于一个右端点,我们要保证从1到该端点的元素和没有重复的元素,于是我们仿照树状数组的做法对于一个相同的元素在之前减掉,同时建一颗主席树即可。

这样我们就可以在线的解决掉这个问题了。

#include <bits/stdc++.h>

using namespace std;

#define LL long long
#define reg register
#define debug(x) cerr<<#x<<" = "<<x<<endl;
#define rep(a,b,c) for(reg int a=(b),a##_end_=(c); a<=a##_end_; ++a)
#define ret(a,b,c) for(reg int a=(b),a##_end_=(c); a<a##_end_; ++a)
#define drep(a,b,c) for(reg int a=(b),a##_end_=(c); a>=a##_end_; --a)

inline int Read() {
    int res=0,f=1;
    char c;
    while(c=getchar(),c<48||c>57)if(c=='-')f=0;
    do res=(res<<3)+(res<<1)+(c^48);
    while(c=getchar(),c>=48&&c<=57);
    return f?res:-res;
}

template<class T>inline bool Min(T &a,T const&b) {
    return a>b?a=b,1:0;
}
template<class T>inline bool Max(T &a,T const&b) {
    return a<b?a=b,1:0;
}

const int N=30005;

int n,m,cnt,root[N];

struct HJT {
    int L,R,Lson,Rson;
    LL tot;
} Tree[N*40];

inline void up(int num) {
    Tree[num].tot=Tree[Tree[num].Lson].tot+Tree[Tree[num].Rson].tot;
}

int build(int L,int R) {
    int p=++cnt;
    Tree[p].L=L,Tree[p].R=R,Tree[p].tot=0;
    if(L==R)return p;
    int mid=(L+R)>>1;
    Tree[p].Lson=build(L,mid);
    Tree[p].Rson=build(mid+1,R);
    return p;
}

int update(int old,int pos,int val) {
    int p=++cnt;
    Tree[p]=Tree[old];
    if(Tree[p].L==Tree[p].R) {
        Tree[p].tot+=1ll*val;
        return p;
    }
    int mid=(Tree[p].L+Tree[p].R)>>1;
    if(pos<=mid)Tree[p].Lson=update(Tree[old].Lson,pos,val);
    else Tree[p].Rson=update(Tree[old].Rson,pos,val);
    up(p);
    return p;
}

LL Sum(int x,int L,int R) {
    if(Tree[x].L==L&&Tree[x].R==R)return Tree[x].tot;
    int mid=(Tree[x].L+Tree[x].R)>>1;
    LL Ans=0;
    if(R<=mid)Ans+=Sum(Tree[x].Lson,L,R);
    else if(L>mid)Ans+=Sum(Tree[x].Rson,L,R);
    else Ans+=Sum(Tree[x].Lson,L,mid)+Sum(Tree[x].Rson,mid+1,R);
    return Ans;
}

int A[N],B[N],pre[N];
int main() {
    int T=Read();
    while(T--) {
        cnt=0;
        memset(pre,-1,sizeof pre);
        n=Read();
        root[0]=build(1,n);
        rep(i,1,n)A[i]=B[i]=Read();
        sort(B+1,B+n+1);
        int len=unique(B+1,B+n+1)-B-1;
        rep(i,1,n) {
            int x=A[i],id=lower_bound(B+1,B+len+1,A[i])-B;
            if(pre[id]==-1)root[i]=update(root[i-1],i,x);
            else {
                int Now=update(root[i-1],pre[id],-x);
                root[i]=update(Now,i,x);
            }
            pre[id]=i;
        }
        m=Read();
        rep(i,1,m) {
            int l=Read(),r=Read();
            printf("%lld\n",Sum(root[r],l,r));
        }
    }
}

同时,主席树不一定是只能执行序列上的操作。

能够执行很多树上以及图上的操作。

大部分有前缀性质的都可以执行。

像如果在树上前缀和,主席树的相减就要用到树上差分的思想相减。

来看看这道题

Count on a tree

如此相似的区间第\(k\)值,那咋办呀?

我们在询问区间的第\(k\)值时,要去维护一个序列的前缀,同样,树上的一条路径是唯一的一条路径。

相似的,我们可以像维护序列的方法去维护一段树上的前缀和。

#include <bits/stdc++.h>

using namespace std;

#define LL long long
#define reg register
#define debug(x) cerr<<#x<<" = "<<x<<endl;
#define rep(a,b,c) for(reg int a=(b),a##_end_=(c); a<=a##_end_; ++a)
#define ret(a,b,c) for(reg int a=(b),a##_end_=(c); a<a##_end_; ++a)
#define drep(a,b,c) for(reg int a=(b),a##_end_=(c); a>=a##_end_; --a)

inline int Read() {
	int res=0,f=1;
	char c;
	while(c=getchar(),c<48||c>57)if(c=='-')f=0;
	do res=(res<<3)+(res<<1)+(c^48);
	while(c=getchar(),c>=48&&c<=57);
	return f?res:-res;
}

template<class T>inline bool Min(T &a,T const&b) {
	return a>b?a=b,1:0;
}
template<class T>inline bool Max(T &a,T const&b) {
	return a<b?a=b,1:0;
}

const int N=1e5+5;

int n,m,cnt,Case,root[N];

struct HJT {
	int Lson,Rson,tot;
} Tree[N*40];

int build(int L,int R) {
	int p=++cnt;
	Tree[p].tot=0;
	if(L==R)return p;
	int mid=(L+R)>>1;
	Tree[p].Lson=build(L,mid);
	Tree[p].Rson=build(mid+1,R);
	return p;
}

int update(int old,int L,int R,int pos,int val) {
	int p=++cnt;
	Tree[p]=Tree[old];
	if(L==R) {
		Tree[p].tot+=val;
		return p;
	}
	int mid=(L+R)>>1;
	if(pos<=mid)Tree[p].Lson=update(Tree[old].Lson,L,mid,pos,val);
	else Tree[p].Rson=update(Tree[old].Rson,mid+1,R,pos,val);
	Tree[p].tot=Tree[Tree[p].Lson].tot+Tree[Tree[p].Rson].tot;
	return p;
}

int A[N],B[N],Fa[N][20],dep[N],len;

vector<int>edge[N];

void dfs(int x,int pre) {
	Fa[x][0]=pre,dep[x]=dep[pre]+1;
	root[x]=update(root[pre],1,len,A[x],1);
	ret(i,0,edge[x].size()) {
		int y=edge[x][i];
		if(y==pre)continue;
		dfs(y,x);
	}
}

inline int up(int x,int step) {
	rep(i,0,19)if(step&(1<<i))x=Fa[x][i];
	return x;
}

inline int lca(int a,int b) {
	if(dep[a]>dep[b])swap(a,b);
	b=up(b,dep[b]-dep[a]);
	if(a==b)return a;
	drep(i,19,0)if(Fa[a][i]!=Fa[b][i])a=Fa[a][i],b=Fa[b][i];
	return Fa[a][0];
}

int ask(int x,int y,int z,int p,int l,int r,int kth) {
	if(l==r)return l;
	int mid=(l+r)>>1;
	int ltot=Tree[Tree[x].Lson].tot+Tree[Tree[y].Lson].tot-Tree[Tree[z].Lson].tot-Tree[Tree[p].Lson].tot;
	if(kth<=ltot)return ask(Tree[x].Lson,Tree[y].Lson,Tree[z].Lson,Tree[p].Lson,l,mid,kth);
	else return ask(Tree[x].Rson,Tree[y].Rson,Tree[z].Rson,Tree[p].Rson,mid+1,r,kth-ltot);
}

int main() {
	n=Read(),m=Read();
	rep(i,1,n)A[i]=B[i]=Read();
	sort(B+1,B+n+1);
	len=unique(B+1,B+n+1)-B-1;
	rep(i,1,n)A[i]=lower_bound(B+1,B+len+1,A[i])-B;
	root[0]=build(1,len);
	ret(i,1,n) {
		int u=Read(),v=Read();
		edge[u].push_back(v);
		edge[v].push_back(u);
	}
	dfs(1,0);
	rep(j,1,19)rep(i,1,n)Fa[i][j]=Fa[Fa[i][j-1]][j-1];
	rep(i,1,m) {
		int u=Read(),v=Read(),k=Read(),z=lca(u,v);
		int Ans=ask(root[u],root[v],root[z],root[Fa[z][0]],1,len,k);
		printf("%d\n",B[Ans]);
	}
}

区间修改的主席树。

To the moon

注意:主席树区间修改要标记永久化,不能延迟标记

#include <bits/stdc++.h>

using namespace std;

#define LL long long
#define reg register
#define debug(x) cerr<<#x<<" = "<<x<<endl;
#define rep(a,b,c) for(reg int a=(b),a##_end_=(c); a<=a##_end_; ++a)
#define ret(a,b,c) for(reg int a=(b),a##_end_=(c); a<a##_end_; ++a)
#define drep(a,b,c) for(reg int a=(b),a##_end_=(c); a>=a##_end_; --a)

inline int Read() {
	int res=0,f=1;
	char c;
	while(c=getchar(),c<48||c>57)if(c=='-')f=0;
	do res=(res<<3)+(res<<1)+(c^48);
	while(c=getchar(),c>=48&&c<=57);
	return f?res:-res;
}

template<class T>inline bool Min(T &a,T const&b) {
	return a>b?a=b,1:0;
}
template<class T>inline bool Max(T &a,T const&b) {
	return a<b?a=b,1:0;
}

const int N=100005;

int n,m,cnt,Case,root[N],A[N];

struct HJT {
	int Lson,Rson;
	LL tot,laze;
} Tree[N*25];

int build(int L,int R) {
	int p=++cnt;
	Tree[p].tot=0;
	if(L==R) {
		Tree[p].tot+=1ll*A[L];
		return p;
	}
	int mid=(L+R)>>1;
	Tree[p].Lson=build(L,mid);
	Tree[p].Rson=build(mid+1,R);
	Tree[p].tot=Tree[Tree[p].Lson].tot+Tree[Tree[p].Rson].tot;
	return p;
}

int update(int old,int l,int r,int L,int R,int val) {
	int p=++cnt;
	Tree[p]=Tree[old];Tree[p].tot+=1ll*val*(R-L+1);
	if(l==L&&R==r) {
		Tree[p].laze+=val;
		return p;
	}
	int mid=(l+r)>>1;
	if(R<=mid)Tree[p].Lson=update(Tree[old].Lson,l,mid,L,R,val);
	else if(L>mid)Tree[p].Rson=update(Tree[old].Rson,mid+1,r,L,R,val);
	else Tree[p].Lson=update(Tree[old].Lson,l,mid,L,mid,val),Tree[p].Rson=update(Tree[old].Rson,mid+1,r,mid+1,R,val);
	return p;
}

LL Sum(int x,int l,int r,int L,int R) {
	if(l==L&&r==R)return Tree[x].tot;
	int mid=(l+r)>>1;
	LL Ans=1ll*Tree[x].laze*(R-L+1);
	if(R<=mid)return Ans+Sum(Tree[x].Lson,l,mid,L,R);
	else if(L>mid)return Ans+Sum(Tree[x].Rson,mid+1,r,L,R);
	else return Ans+Sum(Tree[x].Lson,l,mid,L,mid)+Sum(Tree[x].Rson,mid+1,r,mid+1,R);
}

int tim;

char S[2];

int main() {
	n=Read(),m=Read();
	rep(i,1,n)A[i]=Read();
	root[0]=build(1,n);
	rep(i,1,m) {
		scanf("%s",S);
		if(S[0]=='C') {
			int L=Read(),R=Read(),d=Read();
			root[++tim]=update(root[tim-1],1,n,L,R,d);
		}
		if(S[0]=='Q') {
			int L=Read(),R=Read();
			printf("%lld\n",Sum(root[tim],1,n,L,R));
		}
		if(S[0]=='H') {
			int L=Read(),R=Read(),T=Read();
			printf("%lld\n",Sum(root[T],1,n,L,R));
		}
		if(S[0]=='B')tim=Read();
	}
}

ZOJ2112

之前我们所遇到的第\(k\)值都是静态的。

现在我们遇到的是动态区间第\(k\)值,我们需要换一种方法。

我们对于维护的前缀和利用树状数组分割成\(log_n\)块子序列,利用分块的思想每次将以此操作分解成\(log_n\)次小操作。

对于一次查询操作,我们将两个左右端点的\(log_n\)个有关节点加到两边,每次对于两边的前缀和进行统计操作。

同样的对于每次修改操作,我们也将\(log_n\)个节点的主席树给改变掉。

同时,由于这道题卡内存,我们不能正常的利用线段树去统计。

于是,我们要开两个主席树去维护它,一颗是维护原序列,一颗是代表操作所需的主席树。

#include <cstdio>
#include <cstring>
#include <vector>
#include <iostream>
#include <assert.h>
#include <algorithm>

using namespace std;

#define LL long long
#define reg register
#define clr(a,b) memset(a,b,sizeof a)
#define Mod(x) (x>=mod)&&(x-=mod)
#define debug(x) cerr<<#x<<"="<<x<<endl;
#define debug2(x,y) cerr<<#x<<"="<<x<<" "<<#y<<"="<<y<<endl;
#define debug3(x,y,z) cerr<<#x<<"="<<x<<" "<<#y<<"="<<y<<" "<<#z<<"="<<z<<endl;
#define rep(a,b,c) for(reg int a=(b),a##_end_=(c); a<=a##_end_; ++a)
#define ret(a,b,c) for(reg int a=(b),a##_end_=(c); a<a##_end_; ++a)
#define drep(a,b,c) for(reg int a=(b),a##_end_=(c); a>=a##_end_; --a)
#define erep(i,G,x) for(int i=(G).Head[x]; i; i=(G).Nxt[i])

inline int Read(void) {
	int res=0,f=1;
	char c;
	while(c=getchar(),c<48||c>57)if(c=='-')f=0;
	do res=(res<<3)+(res<<1)+(c^48);
	while(c=getchar(),c>=48&&c<=57);
	return f?res:-res;
}

template<class T>inline bool Min(T &a, T const&b) {
	return a>b?a=b,1:0;
}
template<class T>inline bool Max(T &a, T const&b) {
	return a<b?a=b,1:0;
}
const int N=5e4+5,M=1e4+5,mod=1e9+7;

bool MOP1;

int n,m,A[N],B[N<<1],root[N],Root[N],cnt1,cnt2,Lt[N],Rt[N],len;

struct Function_SEG {
	int cnt,Ls[N*50],Rs[N*50],tot[N*50];
	inline void clear(void) {
		cnt=0,clr(tot,0);
	}
	void Insert(int &Now,int od,int L,int R,int x,int v) {
		Now=++cnt;
		Ls[Now]=Ls[od],Rs[Now]=Rs[od],tot[Now]=tot[od]+v;
		if(L==R)return;
		int mid=(L+R)>>1;
		if(x<=mid)Insert(Ls[Now],Ls[od],L,mid,x,v);
		else Insert(Rs[Now],Rs[od],mid+1,R,x,v);
	}
	int query(int rt,int lt,int L,int R,int k) {
		if(L==R)return L;
		int mid=(L+R)>>1,Sum=tot[Ls[rt]]-tot[Ls[lt]];
		rep(i,1,cnt1)Sum-=tot[Ls[Lt[i]]];
		rep(i,1,cnt2)Sum+=tot[Ls[Rt[i]]];
		if(Sum<k) {
			rep(i,1,cnt1)Lt[i]=Rs[Lt[i]];
			rep(i,1,cnt2)Rt[i]=Rs[Rt[i]];
			return query(Rs[rt],Rs[lt],mid+1,R,k-Sum);
		} else {
			rep(i,1,cnt1)Lt[i]=Ls[Lt[i]];
			rep(i,1,cnt2)Rt[i]=Ls[Rt[i]];
			return query(Ls[rt],Ls[lt],L,mid,k);
		}
	}
} tr;

struct Que {
	int op,L,R,K;
} Q[N];

char S[5];

bool MOP2;

void _main(void) {
	int T=Read();
	while(T--) {
		clr(root,0),clr(Root,0);
		tr.clear();
		n=Read(),m=Read();
		int tot=0;
		rep(i,1,n)A[i]=B[++tot]=Read();
		rep(i,1,m) {
			scanf("%s",S);
			if(S[0]=='Q')Q[i].op=1,Q[i].L=Read(),Q[i].R=Read(),Q[i].K=Read();
			else Q[i].op=2,Q[i].L=Read(),B[++tot]=Q[i].R=Read();
		}
		sort(B+1,B+tot+1);
		len=unique(B+1,B+tot+1)-B-1;
		rep(i,1,n) {
			A[i]=lower_bound(B+1,B+len+1,A[i])-B;
			tr.Insert(root[i],root[i-1],1,len,A[i],1);
		}
		rep(i,1,m) {
			if(Q[i].op==2) {
				Q[i].R=lower_bound(B+1,B+len+1,Q[i].R)-B;
				int pos=Q[i].L,x=A[Q[i].L],y=Q[i].R;
				while(pos<=n) {
					tr.Insert(Root[pos],Root[pos],1,len,x,-1);
					tr.Insert(Root[pos],Root[pos],1,len,y,1);
					pos+=pos&-pos;
				}
				A[Q[i].L]=Q[i].R;
			} else {
				cnt1=cnt2=0;
				for(reg int j=Q[i].L-1; j; j-=j&-j)Lt[++cnt1]=Root[j];
				for(reg int j=Q[i].R; j; j-=j&-j)Rt[++cnt2]=Root[j];
				printf("%d\n",B[tr.query(root[Q[i].R],root[Q[i].L-1],1,len,Q[i].K)]);
			}
		}
	}
}

signed main() {
	_main();
	return 0;
}

[BZOJ3123][SDOI2013]森林

还是区间第\(k\)值,由于题目中保证了任意时刻图都是一个森林。

所以\(x\)\(y\)的路径是唯一的,我们又可以用上文的树上前缀和去维护了。。。

由于有连边操作,就相当于合并两个主席树,直接启发式合并利用倍增维护\(lca\)即可。

#include <bits/stdc++.h>
 
using namespace std;
 
#define LL long long
#define reg register
#define clr(a,b) memset(a,b,sizeof a)
#define Mod(x) (x>=mod)&&(x-=mod)
#define debug(x) cerr<<#x<<"="<<x<<endl;
#define debug2(x,y) cerr<<#x<<"="<<x<<" "<<#y<<"="<<y<<endl;
#define debug3(x,y,z) cerr<<#x<<"="<<x<<" "<<#y<<"="<<y<<" "<<#z<<"="<<z<<endl;
#define rep(a,b,c) for(reg int a=(b),a##_end_=(c); a<=a##_end_; ++a)
#define ret(a,b,c) for(reg int a=(b),a##_end_=(c); a<a##_end_; ++a)
#define drep(a,b,c) for(reg int a=(b),a##_end_=(c); a>=a##_end_; --a)
#define erep(i,G,x) for(int i=(G).Head[x]; i; i=(G).Nxt[i])
 
inline int Read(void) {
    int res=0,f=1;
    char c;
    while(c=getchar(),c<48||c>57)if(c=='-')f=0;
    do res=(res<<3)+(res<<1)+(c^48);
    while(c=getchar(),c>=48&&c<=57);
    return f?res:-res;
}
 
template<class T>inline bool Min(T &a, T const&b) {
    return a>b?a=b,1:0;
}
template<class T>inline bool Max(T &a, T const&b) {
    return a<b?a=b,1:0;
}
const int N=8e4+5,M=8e4+5,mod=1e9+7;
 
bool MOP1;
 
struct Link_list {
    int Tot,Head[N],to[M<<1],Nxt[M<<1];
    inline void clear(void) {
        Tot=0,clr(Head,0);
    }
    inline void AddEdgepair(int a,int b) {
        to[++Tot]=b,Nxt[Tot]=Head[a],Head[a]=Tot;
        to[++Tot]=a,Nxt[Tot]=Head[b],Head[b]=Tot;
    }
} G;
 
int A[N],B[N],root[N];
 
struct Function_SEG {
    int cnt,Ls[N*200],Rs[N*200],tot[N*200];
    void clear(void) {
        cnt=0,clr(Ls,0),clr(Rs,0),clr(tot,0);
    }
    void Insert(int &Now,int pre,int L,int R,int x) {
        Now=++cnt;
        Ls[Now]=Ls[pre],Rs[Now]=Rs[pre],tot[Now]=tot[pre]+1;
        if(L==R)return;
        int mid=(L+R)>>1;
        if(x<=mid)Insert(Ls[Now],Ls[pre],L,mid,x);
        else Insert(Rs[Now],Rs[pre],mid+1,R,x);
        tot[Now]=tot[Ls[Now]]+tot[Rs[Now]];
    }
    int query(int x,int y,int z,int p,int L,int R,int k) {
        if(L==R)return L;
        int mid=(L+R)>>1;
        int Sum=tot[Ls[x]]+tot[Ls[y]]-tot[Ls[z]]-tot[Ls[p]];
        if(k<=Sum)return query(Ls[x],Ls[y],Ls[z],Ls[p],L,mid,k);
        return query(Rs[x],Rs[y],Rs[z],Rs[p],mid+1,R,k-Sum);
    }
} tr;
 
int Sz[N],Id[N],Fa[N][18],dep[N],len;
void dfs(int x,int pre,int tp) {
    Sz[Id[x]=tp]++,Fa[x][0]=pre,dep[x]=dep[pre]+1;
    rep(i,1,17)Fa[x][i]=Fa[Fa[x][i-1]][i-1];
    tr.Insert(root[x],root[pre],1,len,A[x]);
    erep(i,G,x) {
        int y=G.to[i];
        if(y==pre)continue;
        dfs(y,x,tp);
    }
}
 
inline int lca(int x,int y) {
    if(dep[x]>dep[y])swap(x,y);
    int step=dep[y]-dep[x];
    drep(i,17,0)if(step&1<<i)y=Fa[y][i];
    if(x==y)return x;
    drep(i,17,0)if(Fa[x][i]!=Fa[y][i])x=Fa[x][i],y=Fa[y][i];
    return Fa[x][0];
}
 
char S[2];
 
bool MOP2;
 
void _main(void) {
    int T;
    while(~scanf("%d",&T)) {
        int n=Read(),m=Read(),q=Read(),Ans=0;
        rep(i,1,n)root[i]=Sz[i]=Id[i]=0,A[i]=B[i]=Read();
        sort(B+1,B+n+1);
        len=unique(B+1,B+n+1)-B-1;
        rep(i,1,n)A[i]=lower_bound(B+1,B+len+1,A[i])-B;
        rep(i,1,m) {
            int a=Read(),b=Read();
            G.AddEdgepair(a,b);
        }
        rep(i,1,n)if(!Fa[i][0])dfs(i,0,i);
        while(q--) {
            scanf("%s",S);
            int x=Read()^Ans,y=Read()^Ans;
            if(S[0]=='Q') {
                int k=Read()^Ans,LCA=lca(x,y);
                Ans=B[tr.query(root[x],root[y],root[Fa[LCA][0]],root[LCA],1,len,k)];
                printf("%d\n",Ans);
            } else {
                G.AddEdgepair(x,y);
                if(Sz[Id[x]]>Sz[Id[y]])swap(x,y);
                dfs(x,y,Id[y]);
            }
        }
    }
}
 
signed main() {
    _main();
    return 0;
}

[国家集训队]middle

这道题才是真正的用到了主席树的本质。(之前的都是区间\(k\)值)

这道题若考虑整个序列上的中位数,我们可以二分求解。

对于当前二分值\(x\),我们将所有小于它的数都标为\(-1\),其余的数都标为\(1\),最后若该序列的\(tot>=0\)则表示是可行。

现在让我们拓展到一般的情况,我们可以同样对于每一个二分的值建一颗主席树,同时我们统计答案。

我们该如何统计答案呢?

左端点在区间\([a,b]\)中,右端点在区间\([c,d]\)中,所以中间的\([b+1,c-1]\)是必定有的。

让后如果要让答案最大,那就是让\(tot\)越大越好,所以我们自然是要维护\([a,b]\)最大的​后缀和以及\([c,d]\)最大的前缀和。

#include <bits/stdc++.h>
 
using namespace std;
 
#define LL long long
#define reg register
#define clr(a,b) memset(a,b,sizeof a)
#define Mod(x) (x>=mod)&&(x-=mod)
#define debug(x) cerr<<#x<<"="<<x<<endl;
#define debug2(x,y) cerr<<#x<<"="<<x<<" "<<#y<<"="<<y<<endl;
#define debug3(x,y,z) cerr<<#x<<"="<<x<<" "<<#y<<"="<<y<<" "<<#z<<"="<<z<<endl;
#define rep(a,b,c) for(reg int a=(b),a##_end_=(c); a<=a##_end_; ++a)
#define ret(a,b,c) for(reg int a=(b),a##_end_=(c); a<a##_end_; ++a)
#define drep(a,b,c) for(reg int a=(b),a##_end_=(c); a>=a##_end_; --a)
#define erep(i,G,x) for(int i=(G).Head[x]; i; i=(G).Nxt[i])
 
inline int Read(void) {
    int res=0,f=1;
    char c;
    while(c=getchar(),c<48||c>57)if(c=='-')f=0;
    do res=(res<<3)+(res<<1)+(c^48);
    while(c=getchar(),c>=48&&c<=57);
    return f?res:-res;
}
 
template<class T>inline bool Min(T &a, T const&b) {
    return a>b?a=b,1:0;
}
template<class T>inline bool Max(T &a, T const&b) {
    return a<b?a=b,1:0;
}
const int N=2e4+5,M=8e4+5,mod=1e9+7;
 
bool MOP1;
 
int n,B[N],root[N];
 
struct Function_SEG {
    int cnt,Ls[N*200],Rs[N*200];
    struct node {
        int lsum,rsum,sum;
    } V[N*200];
    void up(int num) {
        V[num].lsum=max(V[Ls[num]].sum+V[Rs[num]].lsum,V[Ls[num]].lsum);
        V[num].rsum=max(V[Rs[num]].sum+V[Ls[num]].rsum,V[Rs[num]].rsum);
        V[num].sum=V[Ls[num]].sum+V[Rs[num]].sum;
    }
    void build(int &Now,int L,int R) {
        Now=++cnt;
        if(L==R) {
            V[Now]=(node)<%1,1,1%>;
            return;
        }
        int mid=(L+R)>>1;
        build(Ls[Now],L,mid);
        build(Rs[Now],mid+1,R);
        up(Now);
    }
    void Insert(int &Now,int pre,int L,int R,int x) {
        Now=++cnt;
        Ls[Now]=Ls[pre],Rs[Now]=Rs[pre];
        if(L==R) {
            V[Now]=(node)<%-1,-1,-1%>;
            return;
        }
        int mid=(L+R)>>1;
        if(x<=mid)Insert(Ls[Now],Ls[pre],L,mid,x);
        else Insert(Rs[Now],Rs[pre],mid+1,R,x);
        up(Now);
    }
    node query(int Now,int L,int R,int l,int r) {
        if(!Now||l>r)return (node)<%0,0,0%>;
        if(L==l&&R==r)return V[Now];
        int mid=(L+R)>>1;
        if(r<=mid)return query(Ls[Now],L,mid,l,r);
        else if(l>mid)return query(Rs[Now],mid+1,R,l,r);
        else {
            node lso=query(Ls[Now],L,mid,l,mid),rso=query(Rs[Now],mid+1,R,mid+1,r),An;
            An.lsum=max(lso.sum+rso.lsum,lso.lsum);
            An.rsum=max(rso.sum+lso.rsum,rso.rsum);
            An.sum=lso.sum+rso.sum;
            return An;
        }
    }
} tr;
 
inline bool check(int x,int a,int b,int c,int d) {
    int tot1=tr.query(root[x],1,n,a,b).rsum;
    int tot2=tr.query(root[x],1,n,b+1,c-1).sum;
    int tot3=tr.query(root[x],1,n,c,d).lsum;
    return (tot1+tot2+tot3)>=0;
}
 
struct Node {
    int val,pos;
    bool operator<(Node _)const {
        return val<_.val;
    }
} A[N];
 
bool MOP2;
 
void _main(void) {
    n=Read();
    rep(i,1,n)A[i].val=Read(),A[i].pos=i;
    tr.build(root[1],1,n);
    sort(A+1,A+n+1);
    rep(i,2,n)tr.Insert(root[i],root[i-1],1,n,A[i-1].pos);
    int q=Read(),Ans=0;
    while(q--) {
        rep(i,1,4)B[i]=(Read()+Ans)%n+1;
        sort(B+1,B+4+1);
        int L=1,R=n,Res=0;
        while(L<=R) {
            int mid=(L+R)>>1;
            if(check(mid,B[1],B[2],B[3],B[4]))Res=mid,L=mid+1;
            else R=mid-1;
        }
        Ans=A[Res].val;
        printf("%d\n",Ans);
    }
}
 
signed main() {
    _main();
    return 0;
}
posted @ 2019-09-26 21:33  dsjkafdsaf  阅读(220)  评论(0编辑  收藏  举报