可持久化入门
这是一篇面向初学者的可持久入门文章
人道:“入门持久化,出门莫队人”
莫队【传送门】 (别被传送走了啊
前置芝士:
线段树 【传送门】
一些人文芝士
名称?
可持久化数据机构也叫主席树(反正我是这么理解的
还有知乎讨论过这件事情 【传送门】
身世?
和归并树有着千丝万缕的联系
对于大部分的可持久化数据结构题目,归并树不好写而且难调,思维难度也比主席树高一些
很久以前(也不是很久吧),有位巨佬hjt在比赛的时候遇到一道归并树的题目,他可能觉得太烦了,于是想出了一种新的可持久化数据结构,后来称作主席树了。
思考一下为啥叫主席树。
作用?
能在线查询历史版本,仅此而已
优缺点?
优点:
查询,插入都是
支持在线查询
代码非常简洁,好调,而且基本都是一个板子
缺点:
时间常数大,几乎是卡满的, 有道题我甚至没莫队的一半快
空间常数大,一半来说看到
...
..
.
可持久入门
引入
以模板题P3919 可持久化线段树 1为例
当学习可持久化数据结构时,首先要抛开离线的想法
简述
本题要求支持历史版本修改,查询操作
暴力
我们用数组把每一个版本记录下来。
每个新版本就是上一个版本改变了一个数,其他都保持相同。
于是只要开
每次修改是
升级一下?
暴力为什么拉胯,就是因为每次操作只改变一个数,而暴力要将其他的数都拷贝过来,才能保证下次询问到此版本时能提供这些数据。
我们发现这道题真正的难点在于每次版本生成都要供后来的操作修改,所以我们必须要记录下这个版本的信息,如何记录这个版本成为做题关键
这时候就有神仙想到了线段树的好性质:对于单点修改,线段树只被修改了一条链
有个图就好理解了
我们发现,对于单点修改,其被改动只有
可以想到,除去这些被改动的部分,其他的部分是会原封不动的继承给新版本的,因此版本的继承可以转换为:我是一个版本 姐姐共享就行了
我反正理解能力不太好,这个时候就需要几张图
如此我们可以做到
每个人的
Modify:
//pr是上一个版本,找到s位置改为k,和普通线段树是一样的
//以下所有I都是int,个人码风比较毒瘤
I Mdfy(I pr,I a,I b,I s,I k){I x=++trcnt;I mid=a+b>>1;//一个新节点x
if(!(a^b)) return t[x].val=k,x;//!(a^b)就是a==b,函数要反回根x
if(s<=mid) ls(x)=Mdfy(ls(pr),a,mid,s,k),rs(x)=rs(pr);//右儿子是原封不动的
else rs(x)=Mdfy(rs(pr),mid+1,b,s,k),ls(x)=ls(pr);//左儿子是原封不动的
return x;//回根式
}
//如果没返回可能会RE,int函数经典问题
#include<bits/stdc++.h>
using namespace std;
#define I int
#define F(a,b,c) for(register int a=b;a<=c;++a)
#define ll long long
//#define I long long
I R(){int x=0,f=1;char c=getchar();
for(;c<'0'||c>'9';c=getchar()) if(c=='-') f=-1;
for(;c>='0'&&c<='9';c=getchar()) x=(x<<3)+(x<<1)+(c^48);
return x*f;
}
const int N = 1e6+10;
const int L = 21;
I n,m,d[N];
I trcnt,T[N];
struct TREE{
I ls,rs,val;
}t[N*L*2];
#define ls(x) t[x].ls
#define rs(x) t[x].rs
void Bld(I &x,I a,I b){
x=++trcnt;I mid=a+b>>1;
if(!(a^b)) return t[x].val=d[a],void();
Bld(ls(x),a,mid);
Bld(rs(x),mid+1,b);
}
I Mdfy(I pr,I a,I b,I s,I k){I x=++trcnt;I mid=a+b>>1;
if(!(a^b)) return t[x].val=k,x;
if(s<=mid) ls(x)=Mdfy(ls(pr),a,mid,s,k),rs(x)=rs(pr);
else rs(x)=Mdfy(rs(pr),mid+1,b,s,k),ls(x)=ls(pr);
return x;
}
I Qry(I x,I a,I b,I s){I mid=a+b>>1;
if(!(a^b)) return t[x].val;
if(s<=mid) return Qry(ls(x),a,mid,s);
else return Qry(rs(x),mid+1,b,s);
}
signed main(){n=R(),m=R();
F(i,1,n) d[i]=R();
Bld(T[0],1,n);
F(i,1,m){I rec=R(),opt=R(),x=R();
if(opt==1){I y=R();
T[i]=Mdfy(T[rec],1,n,x,y);
}
else T[i]=T[rec],printf("%d\n",Qry(T[rec],1,n,x));
}
return 0;
}
什么是历史版本?
上面这道题是一个纯模板,入门题其实是P3834 可持久化线段树 2
简述
给出一个数组,求区间
思路
这里我就蒙了啊,我当时先做的这道题,看题解直接人傻了,我完全不知道这和权值线段树有什么关系
如何理解“历史版本”是一个关键问题,在这道题,我们可以把区间理解为版本。
然后我们维护一颗权值线段树,就结束了
F(i,1,n) X[i]=a[i]=Read();
sort(X+1,X+n+1);Tail=unique(X+1,X+n+1)-X-1;
F(i,1,n) a[i]=lower_bound(X+1,X+Tail+1,a[i])-X;
此时的
持久++
了解了的权值线段树,我们发现,每插入一个数后,也只修改一条
于是我们考虑第
如何查询呢?
我们发现主席树的一个特性:他的每个版本规格都是一定的。
那么查询
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define oo 0x3f3f3f3f
#define mp make_pair
#define pb push_back
#define ft first
#define sd second
#define ub upper_bound
#define lb lower_bound
#define pii pair<int,int>
void File(){freopen(".in","r",stdin);freopen(".out","w",stdout);}
const int mod=1e9+7;
struct mint{
int x;mint(int o=0){x=o;}
mint&operator+=(mint a){x+=a.x;x>=mod&&(x-=mod);return*this;}
mint&operator-=(mint a){x-=a.x;x<0&&(x+=mod);return*this;}
mint&operator*=(mint a){x=1ll*x*a.x%mod;return*this;}
mint&operator^=(int b){mint a=*this;x=1;while(b)(b&1)&&(*this*=a,1),b>>=1,a*=a;return*this;}
mint&operator/=(mint a){return*this*=(a^=mod-2);}
friend mint operator+(mint a,mint b){return a+=b;}
friend mint operator-(mint a,mint b){return a-=b;}
friend mint operator*(mint a,mint b){return a*=b;}
friend mint operator/(mint a,mint b){return a/=b;}
};
template<class T>void read(T&x){x=0;char c=getchar();bool f=0;for(;!isdigit(c);c=getchar()){f|=c=='-';}for(;isdigit(c);c=getchar()){x=x*10+c-'0';}if(f)x=-x;}
template<class T,class ...ARK>void read(T&x,ARK&...ark){read(x);read(ark...);}
template<class T>void write(T x){if(x<0) putchar('-'),x=-x;if(x>=10) write(x/10);putchar((x%10)^48);}
template<class T,class ...ARK>void write(T x,ARK...ark){write(x);putchar(' ');write(ark...);puts("");}
const int N=2e5+10;
int n,q,m,cnt;
int a[N],rt[N];
int sum[N<<5],L[N<<5],R[N<<5];
vector<int>uni;
int upd(int pre,int l,int r,int val){
int t=++cnt,mid=(l+r)>>1;
L[t]=L[pre],R[t]=R[pre];
sum[t]=sum[pre]+1;
if(l<r){
if(val<=mid) L[t]=upd(L[pre],l,mid,val);
else R[t]=upd(R[pre],mid+1,r,val);
}
return t;
}
int qry(int u,int v,int l,int r,int k){
if(l>=r) return l;
int mid=(l+r)>>1;
int x=sum[L[v]]-sum[L[u]];
if(x>=k) return qry(L[u],L[v],l,mid,k);
else return qry(R[u],R[v],mid+1,r,k-x);
}
signed main(){
read(n,q);
for(int i=1;i<=n;i++) read(a[i]),uni.pb(a[i]);
sort(uni.begin(),uni.end());
m=unique(uni.begin(),uni.end())-uni.begin()+100;
for(int i=1;i<=n;i++){
int t=lb(uni.begin(),uni.end(),a[i])-uni.begin()+1;
rt[i]=upd(rt[i-1],1,m,t);
}
while(q--){
int x,y,z;
read(x,y,z);
int t=qry(rt[x-1],rt[y],1,m,z);
write(uni[t-1]),puts("");
}
return 0;
}
简洁一点?
#include<bits/stdc++.h>
using namespace std;
#define I int
#define F(a,b,c) for(register int a=b;a<=c;++a)
int R(){int x=0,f=1;char c=getchar();for(;c<'0'||c>'9';c=getchar()) if(c=='-') f=-1;
for(;c>='0'&&c<='9';c=getchar()) x=(x<<3)+(x<<1)+(c^48);return x*f;}
const int N = 2e5+10;
const int L = 19;
struct TREE{int ch[2],num;}t[N*L];
int n,m,Tail,trcnt,T[N],X[N],a[N];
I Mdfy(I pr,I a,I b,I S){I x=++trcnt;t[x].num=t[pr].num+1;
if(a^b){I mid=a+b>>1,d=S>mid;
t[x].ch[d]=Mdfy(t[pr].ch[d],d?mid+1:a,d?b:mid,S);
t[x].ch[!d]=t[pr].ch[!d];
}return x;
}
I Qry(I l,I r,I k){I a=1,b=Tail;
while(a^b){I ls=t[t[r].ch[0]].num-t[t[l].ch[0]].num,d=ls<k,mid=a+b>>1;
l=t[l].ch[d],r=t[r].ch[d],k-=d*ls,a=d?mid+1:a,b=d?b:mid;
}return a;
}//splay后遗症
signed main(){n=R(),m=R();
F(i,1,n) X[i]=a[i]=R();
sort(X+1,X+n+1);Tail=unique(X+1,X+n+1)-X-1;
F(i,1,n) T[i]=Mdfy(T[i-1],1,Tail,lower_bound(X+1,X+Tail+1,a[i])-X);
F(i,1,m){int l=R(),r=R(),k=R();
printf("%d\n",X[Qry(T[l-1],T[r],k)]);
}
return 0;
}
类似的题目有几道:
P1383 高级打字机
模板题,加深印象
P1533 可怜的狗狗
需要注意的是有时候查询
P2163 [SHOI2007]园丁的烦恼
园丁的烦恼正解就是离线,内存只给了
P3168 [CQOI2015]任务查询系统
这题主席树是正解,因为强制在线
还可以干啥?
又是一道经典题目,哈哈的项链
介绍一下这道题,这道题主席树是最慢的,早期这题好像还卡过主席树,那时候时限是1.5s,主席树常数大,TLE了,连莫队都打不过(可是能在线啊)
简述
维护
难点在于如何去重,颜色可能会重复,但只能算一次贡献
做法是维护
这和主席树有啥关系呢?
其中一种简单的做法是对于第
#include<bits/stdc++.h>
#define ls(x) t[x].ls
#define rs(x) t[x].rs
using namespace std;
const int maxn = 1e6+10;
int n,q,pre[maxn];
inline int read(){
int x=0,f=1;
char c=getchar();
while(c<'0'||c>'9'){if(c=='-') f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int T[maxn];
int trcnt,rcnt;
struct TREE{
int ls,rs,val;
}t[maxn*24];
inline void Build(int x,int a,int b){
if(a==b) return;
int mid=a+b>>1;
ls(x)=++trcnt,Build(trcnt,a,mid);
rs(x)=++trcnt,Build(trcnt,mid+1,b);
}
inline void Updata(int R,int x,int col,int a,int b){
t[x].val=t[R].val+1;
if(a==b) return;
int mid=a+b>>1;
if(col<=mid){
ls(x)=++trcnt,rs(x)=t[R].rs;
Updata(ls(R),ls(x),col,a,mid);
}
if(col>=mid+1){
rs(x)=++trcnt,ls(x)=t[R].ls;
Updata(rs(R),rs(x),col,mid+1,b);
}
}
inline int Query(int x,int D,int a,int b){
if(b<=D) return t[x].val;
int mid=a+b>>1;
if(mid>=D) return Query(ls(x),D,a,mid);
else return Query(rs(x),D,mid+1,b)+t[ls(x)].val;
}
int x;
int main(){
scanf("%d",&n);
T[0]=++trcnt,Build(trcnt,0,n);
for(register int i=1;i<=n;++i){
x=read();
T[i]=++trcnt;
Updata(T[i-1],trcnt,pre[x],0,n);
pre[x]=i;
}
q=read();
for(register int i=1;i<=q;++i){
int l,r;
l=read(),r=read();
printf("%d\n",Query(T[r],l-1,0,n)-Query(T[l-1],l-1,0,n));
}
return 0;
}
more??
我们发现只要一颗线段树能干,又是单点修改,直接就可以可持久化
看题 P3963 [TJOI2013] 奖学金
简述
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+10;
const int M = N*100;
int n,m,cnt;
int trcnt,T[2][N];
struct TREE{
int val,ls,rs;
}dep[M],fa[M];
void Build(int &x,int a,int b,int data,TREE t[]){
if(!x) x=++trcnt;
if(a==b){t[x].val=(data==0)?a:1;return;}
int mid=a+b>>1;
Build(t[x].ls,a,mid,data,t);
Build(t[x].rs,mid+1,b,data,t);
}
void Updata(int P,int &x,int a,int b,int seat,int data,TREE t[]){
if(!x) x=++trcnt;
t[x]=t[P];
if(a==b){t[x].val=data;return;}
int mid=a+b>>1;
if(seat<=mid){
t[x].ls=0;
Updata(t[P].ls,t[x].ls,a,mid,seat,data,t);
}
else{
t[x].rs=0;
Updata(t[P].rs,t[x].rs,mid+1,b,seat,data,t);
}
}
int Query(int x,int a,int b,int data,TREE t[]){
if(a==b) return t[x].val;
int mid=a+b>>1;
if(data<=mid) return Query(t[x].ls,a,mid,data,t);
else return Query(t[x].rs,mid+1,b,data,t);
}
int find(int x){
int v=Query(T[0][cnt-1],1,n,x,fa);
if(x!=v) return find(v);
return x;
}
void merge(int x,int y){
x=find(x);
y=find(y);
int depx=Query(T[1][cnt-1],1,n,x,dep),depy=Query(T[1][cnt-1],1,n,y,dep);
if(depx<depy) swap(x,y),swap(depx,depy);
Updata(T[0][cnt-1],T[0][cnt],1,n,y,x,fa);
Updata(T[1][cnt-1],T[1][cnt],1,n,x,max(depx,depy+1),dep);
}
int main(){
scanf("%d%d",&n,&m);
Build(T[0][0],1,n,0,fa);
Build(T[1][0],1,n,1,dep);
for(cnt=1;cnt<=m;++cnt){
int opt;
scanf("%d",&opt);
if(opt==1){
int x,y;
scanf("%d%d",&x,&y);
merge(x,y);
}
else if(opt==2){
int pnt;
scanf("%d",&pnt);
T[0][cnt]=T[0][pnt],T[1][cnt]=T[1][pnt];
}
else{
int x,y;
scanf("%d%d",&x,&y);
if(find(x)==find(y)) printf("1\n");
else printf("0\n");
T[0][cnt]=T[0][cnt-1],T[1][cnt]=T[1][cnt-1];
}
return 0;
}
由于这篇文写得有点长了,简述就免了。
more???
CF1000F One Occurrence
简述
按照哈哈的项链,先处理出
那么问题就转化为求取
然后建树,考虑给右端点建树,把对应位置存入主席树里,维护
查询区间的时候把r的树取出来,把这里面
#include<bits/stdc++.h>
using namespace std;
int read(){int x=0,f=1;char c=getchar();
for(;c<'0'||c>'9';c=getchar()) if(c=='-') f=-1;
for(;c>='0'&&c<='9';c=getchar()) x=(x<<3)+(x<<1)+(c^48);
return x*f;
}
const int N = 5e5+10;
const int L = 20;
const int INF = 1e9+10;
const int qwq = 0;
const int qvq = 1;
const int MaxDat = 5e5;
int n,m,pre[N],dat[N];
#define pii pair<int,int>
#define fi first
#define se second
int trcnt,T[N];
struct TREE{
int ls,rs;
pii val;
}t[N*L*2];
#define ls(x) t[x].ls
#define rs(x) t[x].rs
void Push_up(int x){t[x].val=min(t[ls(x)].val,t[rs(x)].val);}
int Mdfy(int pr,int x,int a,int b,int seat,int lim){
if(!x) x=++trcnt,t[x].val.fi=INF;
if(!(a^b)) return t[x].val.fi=lim,t[x].val.se=a,x;
int mid=a+b>>1;
if(seat<=mid) ls(x)=Mdfy(ls(pr),qwq,a,mid,seat,lim),rs(x)=rs(pr);
else rs(x)=Mdfy(rs(pr),qwq,mid+1,b,seat,lim),ls(x)=ls(pr);
Push_up(x);
return x;
}
int Qry(int x,int a,int b,int L,int R){
if(L<=a&&b<=R) return t[x].val.fi<L?t[x].val.se:INF;
int mid=a+b>>1,res=INF;
if(mid>=L) res=min(res,Qry(ls(x),a,mid,L,R));
if(res!=INF) return res;
if(mid+1<=R) res=min(res,Qry(rs(x),mid+1,b,L,R));
return res;
}
signed main(){n=read();
for(int i=1;i<=n;++i){dat[i]=read();int tmp;
if(pre[dat[i]]) tmp=Mdfy(T[i-1],qwq,1,MaxDat,pre[dat[i]],INF);
else tmp=T[i-1];
T[i]=Mdfy(tmp,qwq,1,MaxDat,i,pre[dat[i]]);
pre[dat[i]]=i;
}
m=read();
for(int i=1;i<=m;++i){int l=read(),r=read();
int k=Qry(T[r],1,MaxDat,l,r);
printf("%d\n",dat[k==INF?0:k]);
}
return 0;
}
more????
论思路之拓宽:CF522D Closest Equals
对于区间最近相同数的距离,在预处理的时候只需要考虑相邻两个相同的数的距离,将其预处理出来
预处理
考虑主席树,第
由于主席树节约空间把上一棵树拿来用,这题要满足
#include<bits/stdc++.h>
using namespace std;
const int INF = 1e9+10;
int read(){
int x=0,f=1;char c=getchar();
for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
for(;isdigit(c);c=getchar()) x=(x<<3)+(x<<1)+(c^48);
return x*f;
}
const int N = 5e5+10;
const int L = 20;
int n,m,dat[N],X[N],pre[N];
int trcnt,T[N];
struct TREE{
int minn,ls,rs;
}t[N*L];
#define ls(x) t[x].ls
#define rs(x) t[x].rs
vector<int>G[N];
void Push_up(int x){
int res=INF;
if(ls(x)) res=min(res,t[ls(x)].minn);
if(rs(x)) res=min(res,t[rs(x)].minn);
t[x].minn=res;
}
int Mdfy(int Pre,int x,int a,int b,int seat,int DAT){
if(!x) x=++trcnt;
if(!(a^b)) return t[x].minn=DAT,x;
int mid=a+b>>1;
if(seat<=mid) ls(x)=Mdfy(ls(Pre),ls(x),a,mid,seat,DAT),rs(x)=rs(Pre);
else rs(x)=Mdfy(rs(Pre),rs(x),mid+1,b,seat,DAT),ls(x)=ls(Pre);
Push_up(x);
return x;
}
int Qry(int x,int a,int b,int L,int R){
if(!x) return INF;
if(L<=a&&b<=R) return t[x].minn;
int mid=a+b>>1,res=INF;
if(mid>=L) res=min(res,Qry(ls(x),a,mid,L,R));
if(mid+1<=R) res=min(res,Qry(rs(x),mid+1,b,L,R));
return res;
}
signed main(){
n=read(),m=read();
for(int i=1;i<=n;++i) dat[i]=read(),X[i]=dat[i];
sort(X+1,X+n+1);
int Tail=unique(X+1,X+n+1)-X-1;
for(int i=1,tmp;i<=n;++i){
tmp=lower_bound(X+1,X+Tail+1,dat[i])-X;
if(pre[tmp]) G[pre[tmp]].push_back(i),dat[i]=i-pre[tmp];
pre[tmp]=i;
}
for(int i=n;i>=1;--i){
if(!G[i].size()){T[i]=T[i+1];continue;}
int lst=T[i+1],prs=0;
for(vector<int>::iterator it=G[i].begin();it!=G[i].end();++it){
int gr=(*it);
prs=Mdfy(lst,prs,1,n,gr,dat[gr]);
swap(lst,prs);
}
T[i]=lst;
}
for(int i=1;i<=m;++i){
int l=read(),r=read();
int ans=Qry(T[l],1,n,1,r);
if(ans==INF) puts("-1");
else printf("%d\n",ans);
}
return puts(""),0;
}
还有骚操作
我们逐渐意识到主席树有一种连续性的限制,如果需要第
于是来一道经典题:P4197 Peaks
?
这一看,发现和主席树搭边的就只是第
kruscal重构树
这是一种性质极好的算法,具体了解可以问度娘,这里简述一下。
每次把权值最小的边连向的两个点集拎出来,把这两个点集连到一个新的点上,该点的权值是比边权,用并查集把这些点全部归到新点的点集里。
这样有什么用呢,我们发现对于星(新=星=
更棒的什么呢,由于重构树是树形,代表叶子节点(原始节点)是顺序是按dfs序固定的,那么也就代表,我们的询问在重构树求得的真正区间一定是连续一段,连续,也就代表可以构成可持久化。
#include<bits/stdc++.h>
using namespace std;
int read(){int x=0,f=1;char c=getchar();
for(;c<'0'||c>'9';c=getchar()) if(c=='-') f=-1;
for(;c>='0'&&c<='9';c=getchar()) x=(x<<3)+(x<<1)+(c^48);
return x*f;
}
const int N = 1e6+10;
int n,m,q;
int h[N<<1],dif[N<<1],L[N<<1],R[N<<1],X[N],f[N<<1][25];
struct EDGE{
int f,t,val;
bool operator <(EDGE x)const{
return val<x.val;
}
}e[N];
struct Graph{
int hd[N],nxt[N],to[N],val[N],cnt;
void add(int f,int t){
nxt[++cnt]=hd[f],to[cnt]=t,hd[f]=cnt;
}
}G;
int trcnt,T[N];
struct TREE{
int ls,rs,num;
}t[N*23];
#define ls(x) t[x].ls
#define rs(x) t[x].rs
const int qwq = 0;
int Mdfy(int pr,int x,int a,int b,int seat){if(!x) x=++trcnt;
t[x].num=t[pr].num+1;
if(a^b){int mid=a+b>>1;
if(seat<=mid) ls(x)=Mdfy(ls(pr),qwq,a,mid,seat),rs(x)=rs(pr);
else rs(x)=Mdfy(rs(pr),qwq,mid+1,b,seat),ls(x)=ls(pr);
}
return x;
}
int Qry(int lft,int rht,int a,int b,int k){if(k<=0) return 0;
if(!(a^b)) return a;
int mid=a+b>>1,rsum=t[rs(rht)].num-t[rs(lft)].num;
if(k>rsum) return Qry(ls(lft),ls(rht),a,mid,k-rsum);
else return Qry(rs(lft),rs(rht),mid+1,b,k);
}
int num=0;
void Dfs(int u){
for(int i=1;i<=20;++i) f[u][i]=f[f[u][i-1]][i-1];
if(u<=n){int t=lower_bound(X+1,X+n+1,h[u])-X;
L[u]=R[u]=++num;
T[num]=Mdfy(T[num-1],qwq,1,n,t);
return;
}
for(int i=G.hd[u],gr;i;i=G.nxt[i]){
Dfs(gr=G.to[i]);
L[u]=min(L[u],L[gr]);
R[u]=max(R[u],R[gr]);
}
}
int fa[N];
int Getf(int x){
if(fa[x]^x) fa[x]=Getf(fa[x]);
return fa[x];
}
const int INF = 1e9+10;
void Rebuild(){int cnt=n;
sort(e+1,e+m+1);
for(int i=1;i<=n;++i) fa[i]=i;
for(int i=1;i<=m;++i){int u=Getf(e[i].f),v=Getf(e[i].t);
if(u^v){
dif[++cnt]=e[i].val;//按重构树的叶子结点顺序排序,那么查询得到的区间必然是连续的
fa[u]=fa[v]=fa[cnt]=cnt;
G.add(cnt,v),G.add(cnt,u);
f[u][0]=f[v][0]=cnt;
}
}
for(int i=1;i<=cnt;++i) L[i]=INF,R[i]=-INF;
sort(X+1,X+n+1);
Dfs(cnt);
}
int Findroot(int u,int lim){
for(int i=20;~i;--i)
if(f[u][i]&&dif[f[u][i]]<=lim)
u=f[u][i];
return u;
}
signed main(){n=read(),m=read(),q=read();
for(int i=1;i<=n;++i) X[i]=h[i]=read();
for(int i=1;i<=m;++i) e[i].f=read(),e[i].t=read(),e[i].val=read();
Rebuild();
for(int i=1;i<=q;++i){int v=read(),x=read(),k=read();
int t=Findroot(v,x);
int ans=R[t]-L[t]+1>=k?X[Qry(T[L[t]-1],T[R[t]],1,n,k)]:-1;
printf("%d\n",ans);
}
return 0;
}
【双倍经验】
P4899 [IOI2018] werewolf 狼人
道理是一样的,需要建两棵重构树。
骚操作2:
P4137 Rmq Problem / mex
这道题绝对是小清新题,我最开始用莫队过的,我仔细一想,发现好像可以用主席树,一看题解,真可以。
因为要求
然后对于每棵权值线段树,我们维护每个位置(权值)最后出现的位置(原数列位置)最小是多少。对于区间
#include<bits/stdc++.h>
using namespace std;
int read(){int x=0,f=1;char c=getchar();
for(;c<'0'||c>'9';c=getchar()) if(c=='-') f=-1;
for(;c>='0'&&c<='9';c=getchar()) x=(x<<3)+(x<<1)+(c^48);
return x*f;
}
const int N = 2e5+10;
const int MaxDat = 2e5+10;
int n=read(),m=read(),a[N],X[N<<2],cntX;
int T[N],trcnt;
struct NODE{
int ls,rs,dat;
}t[MaxDat*20];
#define ls(x) t[x].ls
#define rs(x) t[x].rs
int Modify(int Pre,int a,int b,int seat,int dat){
int x=++trcnt,mid=(a+b)>>1;
if(a==b) return t[x].dat=dat,x;
if(seat<=mid) ls(x)=Modify(ls(Pre),a,mid,seat,dat),rs(x)=rs(Pre);
else rs(x)=Modify(rs(Pre),mid+1,b,seat,dat),ls(x)=ls(Pre);
t[x].dat=min(t[ls(x)].dat,t[rs(x)].dat);
return x;
}
int Qry(int x,int a,int b,int cut){
if(!x||a==b) return a;
int mid=(a+b)>>1;
if(t[ls(x)].dat<cut) return Qry(ls(x),a,mid,cut);
else return Qry(rs(x),mid+1,b,cut);
}
signed main() {
X[++cntX]=0;
for(int i=1;i<=n;++i){
a[i]=read();
X[++cntX]=a[i],X[++cntX]=a[i]+1;
}
sort(X+1,X+cntX+1);
int Tail=unique(X+1,X+cntX+1)-X-1;
cntX=Tail;
for(int i=1;i<=n;++i)
T[i]=Modify(T[i-1],1,cntX,lower_bound(X+1,X+cntX+1,a[i])-X,i);
for(int i=1;i<=m;++i){
int l=read(),r=read();
printf("%d\n",X[Qry(T[r],1,cntX,l)]);
}
return 0;
}
骚操作3
P2633 Count on a tree
这真的骚到我了,但也是常规题目,维护第k小就表明这个可持久化是有可减性的,那么我们只要用差分的套路,
传4个变量确实挺不戳的
#include<bits/stdc++.h>
using namespace std;
#define I int
#define F(a,b,c) for(register int a=b;a<=c;++a)
#define ll long long
//#define I long long
I R(){int x=0,f=1;char c=getchar();
for(;c<'0'||c>'9';c=getchar()) if(c=='-') f=-1;
for(;c>='0'&&c<='9';c=getchar()) x=(x<<3)+(x<<1)+(c^48);
return x*f;
}
const int N = 2e5+10;
const int L = 17;
I n,m,a[N],X[N],ans,Tail;
I trcnt,T[N];
struct TREE{
I ls,rs,num;
}t[N*L];
#define ls(x) t[x].ls
#define rs(x) t[x].rs
I Mdfy(I pr,I a,I b,I s){I x=++trcnt,mid=a+b>>1;
t[x].num=t[pr].num+1;
if(!(a^b)) return x;
if(s<=mid) ls(x)=Mdfy(ls(pr),a,mid,s),rs(x)=rs(pr);
else rs(x)=Mdfy(rs(pr),mid+1,b,s),ls(x)=ls(pr);
return x;
}
I Qry(I l,I r,I y,I z,I a,I b,I k){I mid=a+b>>1;
if(!(a^b)) return a;
I sum=t[ls(l)].num+t[ls(r)].num-t[ls(y)].num-t[ls(z)].num;
if(sum>=k) return Qry(ls(l),ls(r),ls(y),ls(z),a,mid,k);
else return Qry(rs(l),rs(r),rs(y),rs(z),mid+1,b,k-sum);
}
struct Graph{
I to[N],nxt[N],hd[N],dep[N],fa[N][L],cnt;
void Adde(I f,I t){nxt[++cnt]=hd[f],to[cnt]=t,hd[f]=cnt;}
#define Fe(x,y) for(int z=hd[x],y=to[z];z;y=to[z=nxt[z]])
void Dfs(I u,I f){
dep[u]=dep[f]+1,fa[u][0]=f;
T[u]=Mdfy(T[f],1,Tail,a[u]);
F(i,1,L-1) fa[u][i]=fa[fa[u][i-1]][i-1];
Fe(u,gr)if(f^gr)Dfs(gr,u);
}
I LCA(I x,I y){
if(dep[x]<dep[y]) swap(x,y);
for(I i=L-1;~i;--i)
if(dep[fa[x][i]]>=dep[y])
x=fa[x][i];
if(!(x^y)) return x;
for(I i=L-1;~i;--i)
if(fa[x][i]^fa[y][i])
x=fa[x][i],y=fa[y][i];
return fa[x][0];
}
}G;
signed main(){n=R(),m=R();
F(i,1,n) X[i]=a[i]=R();
sort(X+1,X+n+1);Tail=unique(X+1,X+n+1)-X-1;
F(i,1,n) a[i]=lower_bound(X+1,X+Tail+1,a[i])-X;
F(i,1,n-1){I u=R(),v=R();
G.Adde(u,v),G.Adde(v,u);
}G.Dfs(1,0);
F(i,1,m){I u=R()^ans,v=R(),k=R(),lca=G.LCA(u,v);
printf("%d\n",ans=X[Qry(T[u],T[v],T[lca],T[G.fa[lca][0]],1,Tail,k)]);
}
return 0;
}
我到底该维护什么?-CF1422F Boring Queries
老
简述
求
我们知道,很多个数的最小公倍数就相当于每个数都质因数分解后,对于每一个质数,取质因数数量的最大作为最小公倍数的那个质因数数量,那么这就是最小公倍数
我们可以考虑根号分块,一个数
用主席树维护其是否出现过,如果出现过,那么也只能出现一次,可以仿照之前HH的项链那种做法,只对它进行一次贡献的计算,但最麻烦的是
其实我们要统计的东西是
using namespace std;
typedef long long ll;
#define pii pair<int,int>
#define mp make_pair
#define fi first
#define se second
const long long mod1 = 1e9+7;
int read(){int x=0,f=1;char c=getchar();
for(;c<'0'||c>'9';c=getchar()) if(c=='-') f=-1;
for(;c>='0'&&c<='9';c=getchar()) x=(x<<3)+(x<<1)+(c^48);
return x*f;
}
const int N = 1e5+2;
const int D = 2e5+2;
const int M = 88;
const int L = 18;
const int Lim = 87;
int n,m;
bool isprime[1000];int dat[N],prime[100],cntP;
void Euler(int x){
memset(isprime,true,sizeof(isprime));
isprime[1]=false;
for(int i=2;i<=x;++i){
if(isprime[i]) prime[++cntP]=i;
for(int j=1;j<=cntP&&i*prime[j]<=x;++j){
isprime[prime[j]*i]=false;
if(i%prime[j]==0) break;
}
}
}
int Quick_Pow(int x,int k){
int res=1;
for(;k;k>>=1,x=x*x%mod1)
if(k&1)
res=res*x%mod1;
return res;
}
int T[N],trcnt;
struct TREE{
int ls,rs;
ll mul=1ll;
}t[N*L];
#define ls(x) t[x].ls
#define rs(x) t[x].rs
int Mdfy(int Pre,int x,int a,int b,int seat,int dat){
if(!x) x=++trcnt;
t[x].mul=t[Pre].mul;
if(!(a^b)) return t[x].mul=t[x].mul*1ll*dat%mod1,x;
int mid=(a+b)>>1;
if(seat<=mid) ls(x)=Mdfy(ls(Pre),ls(x),a,mid,seat,dat),rs(x)=rs(Pre);
else rs(x)=Mdfy(rs(Pre),rs(x),mid+1,b,seat,dat),ls(x)=ls(Pre);
t[x].mul=t[ls(x)].mul*t[rs(x)].mul%mod1;
return x;
}
ll Qry(int x,int a,int b,int L,int R){
if(!x) return 1;
if(L<=a&&b<=R) return t[x].mul;
int mid=(a+b)>>1;ll res=1;
if(mid>=L) res=Qry(ls(x),a,mid,L,R)%mod1;
if(mid+1<=R) res=res*Qry(rs(x),mid+1,b,L,R)%mod1;
return res;
}
int pre[D];
vector<pii> pos[N];
void Pre_(){
for(int i=1;i<=n;++i){
int x=dat[i];
pos[pre[x]].push_back(mp(i,x));
pre[x]=i;
}
for(int i=0;i<=n;++i){
if(pos[i].size()){
int Now=0,Pre=0;
if(i>=1) Pre=T[i-1];
for(vector<pii>::iterator it=pos[i].begin();it!=pos[i].end();++it){
T[i]=Now=Mdfy(Pre,Now,1,n,(*it).fi,(*it).se);
swap(Now,Pre);
}
}
else T[i]=T[i-1];
}
}
char f[M][N][L];int Pow[M][20],lg[N];
void Pre(){
for(int i=1;i<=Lim;++i){
for(int j=1;j<=20;++j)
Pow[i][j]=Quick_Pow(prime[i],j);
Pow[i][0]=1;
}
for(int i=2;i<=n;++i) lg[i]=lg[i>>1]+1;
for(int k=1;k<=Lim;++k){
for(int j=1;j<=lg[n];++j){
for(int i=1;i+(1<<j)-1<=n;++i){
f[k][i][j]=max(f[k][i][j-1],f[k][i+(1<<(j-1))][j-1]);
}
}
}
}
int Query(int k,int l,int r){
int p=lg[r-l+1];
return max(f[k][l][p],f[k][r-(1<<p)+1][p]);
}
signed main(){
n=read();
Euler(450);
for(int i=1;i<=n;++i){
int x=read();
for(int j=1;j<=Lim;++j)
for(;!(x%prime[j]);x/=prime[j])
++f[j][i][0];
dat[i]=x;
}
Pre_();
Pre();
m=read();ll ans=0;
for(register int i=1;i<=m;++i){
int l=(ans+read())%n+1,r=(ans+read())%n+1;
if(l>r) swap(l,r);
ans=1;
for(register int j=1;j<=Lim;++j)
ans=ans*1ll*(Pow[j][Query(j,l,r)])%mod1;
ans=(ans*1ll*Qry(T[l-1],1,n,l,r))%mod1;
printf("%lld\n",ans);
}
return 0;
}
这和主席树有什么关系?-P3293 [SCOI2016]美味
这道题极好,建议别看题解,自己做做
讲讲思路,这是一个经典的贪心,如果我们希望亦或出来的值最大,我们尽可能的要从高位到低位将他按位取反,然后我们发现这样从高位到低位的二分,不正是一半一半的缩短范围。
我们的答案是一位一位推出来的,我们记录
#include<bits/stdc++.h>
#define ls(x) t[x].ls
#define rs(x) t[x].rs
using namespace std;
const int maxn = 2e5+10;
const int maxq = 1e5+10;
const int maxd = 1e5+10;
inline int read(){
int x=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-') f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,m;
int T[maxn],trcnt;
struct TREE{
int num,ls,rs;
}t[maxn*50];
void Updata(int &P,int &x,int a,int b,int data){
if(!P) P=++trcnt;
if(!x) x=++trcnt;
t[x]=t[P],++t[x].num;
if(a==b) return;
int mid=a+b>>1;
if(data<=mid){
ls(x)=0;
Updata(ls(P),ls(x),a,mid,data);
}
else{
rs(x)=0;
Updata(rs(P),rs(x),mid+1,b,data);
}
}
int Query(int &P,int &x,int a,int b,int L,int R){
if(L>R||a>b) return 0;
if(!P) P=++trcnt; if(!x) x=++trcnt;
if(L<=a&&b<=R) return t[x].num-t[P].num;
int mid=a+b>>1,res=0;
if(L<=mid) res+=Query(ls(P),ls(x),a,mid,L,R);
if(R>=mid+1) res+=Query(rs(P),rs(x),mid+1,b,L,R);
return res;
}
int main(){
n=read(),m=read();
for(int i=1;i<=n;++i){
int x=read();
Updata(T[i-1],T[i],0,maxd,x);
}
for(int i=1;i<=m;++i){
int b=read(),x=read(),l=read(),r=read(),ans=0;
for(int j=18;j>=0;--j){
int d=0;
if(((1<<j)&b)&&!Query(T[l-1],T[r],0,maxd,ans-x,ans-x+(1<<j)-1))
ans+=(1<<j);
if(!((1<<j)&b)&&Query(T[l-1],T[r],0,maxd,ans-x+(1<<j),ans-x+(1<<(j+1))-1))
ans+=(1<<j);
}
printf("%d\n",ans^b);
}
return 0;
}
误区
这里有个大误区,就是很多人说有一种叫做“树状数组套主席树”的东西
我在这里辟个谣,其实那玩意儿就是普通的树状数组套权值线段树,只是因为单点修改,程序长得像的不得了,然后就混为一谈了。
具体理论应该是每个点维护对应管辖区间所组成的权值线段树,因为树状数组的特性,不论是修改还是查询,一个区间都可以被分成
还是找道题贴代码:
P2617 Dynamic Rankings
#include<bits/stdc++.h>
using namespace std;
int read(){int x=0,f=1;char c=getchar();
for(;c<'0'||c>'9';c=getchar()) if(c=='-') f=-1;
for(;c>='0'&&c<='9';c=getchar()) x=(x<<3)+(x<<1)+(c^48);
return x*f;
}
const int N = 2e5+10;
int n,m,len,dat[N],X[N];
struct NODE{
bool opt;
int l,r,k,pos,y;
}Ques[N];
int trcnt,T[N];
struct TREE{
int num,ls,rs;
}t[N*400];
#define ls(x) t[x].ls
#define rs(x) t[x].rs
int lowbit(int x){return x&-x;}
int Mdfy(int x,int a,int b,int seat,int add){
if(!x) x=++trcnt;
t[x].num+=add;
if(!(a^b)) return x;
int mid=a+b>>1;
if(seat<=mid) ls(x)=Mdfy(ls(x),a,mid,seat,add);
else rs(x)=Mdfy(rs(x),mid+1,b,seat,add);
return x;
}
void PreMdfy(int x,int add){
int k=lower_bound(X+1,X+len+1,dat[x])-X;
for(int i=x;i<=n;i+=lowbit(i))
T[i]=Mdfy(T[i],1,len,k,add);
}
int tmp[2][N],cnt[2];
int Qry(int a,int b,int k){
if(!(a^b)) return a;
int mid=a+b>>1,sum=0;
for(int i=1;i<=cnt[1];++i) sum+=t[ls(tmp[1][i])].num;
for(int i=1;i<=cnt[0];++i) sum-=t[ls(tmp[0][i])].num;
if(k<=sum){
for(int i=1;i<=cnt[1];++i) tmp[1][i]=ls(tmp[1][i]);
for(int i=1;i<=cnt[0];++i) tmp[0][i]=ls(tmp[0][i]);
return Qry(a,mid,k);
}
else{
for(int i=1;i<=cnt[1];++i) tmp[1][i]=rs(tmp[1][i]);
for(int i=1;i<=cnt[0];++i) tmp[0][i]=rs(tmp[0][i]);
return Qry(mid+1,b,k-sum);
}
}
int PreQry(int l,int r,int k){
cnt[0]=cnt[1]=0;
for(int i=r;i;i-=lowbit(i)) tmp[1][++cnt[1]]=T[i];//取出r的那些权值线段树
for(int i=l-1;i;i-=lowbit(i)) tmp[0][++cnt[0]]=T[i];//取出l-1的那些权值线段树
return Qry(1,len,k);
}
signed main(){
n=read(),m=read();
for(int i=1;i<=n;++i) dat[i]=read(),X[++len]=dat[i];
for(int i=1;i<=m;++i){char c;cin>>c;
if(c=='Q'){
int l=read(),r=read(),k=read();
Ques[i].opt=true,Ques[i].l=l,Ques[i].r=r,Ques[i].k=k;
}
else{
int pos=read(),u=read();
X[++len]=u;
Ques[i].opt=false,Ques[i].pos=pos,Ques[i].y=u;
}
}
sort(X+1,X+len+1);
int Tail=unique(X+1,X+len+1)-X-1;len=Tail;
for(int i=1;i<=n;++i) PreMdfy(i,1);
for(int i=1;i<=m;++i){
if(Ques[i].opt){
printf("%d\n",X[PreQry(Ques[i].l,Ques[i].r,Ques[i].k)]);
}
else{
PreMdfy(Ques[i].pos,-1);
dat[Ques[i].pos]=Ques[i].y;
PreMdfy(Ques[i].pos,1);
}
}
return 0;
}
然后您就入门成功啦~
一些练习题:
P7577 「PMOI-3」简单模拟题
毒瘤题,我最开始连二分都没想到(出题人好像还和某篇题解吵了起来)
P4559 [JSOI2018]列队
列队绿色小清新
P4592 [TJOI2018]异或
可持久化trie?
P4735 最大异或和
我仔细一想,这玩意儿真的是可持久化trie
P2048 [NOI2010] 超级钢琴
可以练练手,但我建议用题解的那种方法做
P5283 [十二省联考 2019] 异或粽子
和上面的一题一样
P3567 [POI2014]KUR-Couriers
无脑题
P4094 [HEOI2016/TJOI2016]字符串
二分套SA套主席树,大常数,大容量,我不会
P5385 [Cnoi2019]须臾幻境
经典套lct,码量居然比上面那题小。
P7357 「PMOI-1」中位数
这题我也没做过,看上去不错
P6214 「SWTR-04」Taking a Walk
这是一道经典的不可做题,只是个摆设罢了
夹带私货
绘图工具 draw.io/diagrams
一个极适合
发现
洛谷的图片空间不够用怎么办,
一些话
快去学莫队吧,
这篇文大概写了我
写在最后
谢谢你