wls 的树(dfs序主席树+并查集+lca)
Problem Description
wls有一棵有根树,其中的点从1到n标号,其中1是树根。每次wls可以执行两种操作中的一个:
(1)选定一个点x,将以x为根的子树变成一条按照编号排序的链,其中编号最大的作为新的子树的根(成为原来x的父亲节点的儿子,如果原来x没有父亲节点则新的子树的根也没有父亲节点)。
(2)查询两个点之间的最短路径上经过了多少边。
Input
第一行一个整数tt表示数据组数(t≤10)。
每组数据第一行一个正整数n表示树上的点数(1≤n≤100000)。
接下来n−1行每行两个1到n之间的正整数表示一条树边。
接下来一行一个正整数q表示询问的个数(1≤q≤200000)。
接下来q行每行表示一个操作。第一种操作格式为1 x,其中x为指定的树根。第二种操作格式为2 x y,表示查询从x到y的路径。
Output
对于每个第二种操作,输出一行一个正整数表示答案。
解1:对于1操作,可以用并查集缩到最靠近根节点(1)的点上;
对于2操作,可以判断用并查集可以判断两点是否被缩点过,对于缩点的集合,由于该集合又是一颗子树,dfs序是连续的,因此可以用主席树来求比x大的有多少个,接下来分情况讨论
两点x,y分别被缩在fx,fy两个点上,x所在集合中比x大的有cx个,y同理cy个;
1、x,y两点被缩在一个集合中,ans=abs(cx-cy);
2、不在一个集合中,ans=cx+cy+(dep[fx]+dep[fy]-2*dep[lca(fx,fy)])(树上两点距离)
代码:
/**
* author: IQ^QI
* created: 26.08.2019
**/
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+9;
int t,n,q;
vector<int>g[N];
int dfn[N],rk[N],num,siz[N],f[N][21],dep[N];
int lca(int x,int y){
if(dep[x]<dep[y])swap(x,y);
int d=dep[x]-dep[y];
for(int i=20;i>=0;i--)if(d&(1<<i))x=f[x][i];
if(x==y)return x;
for(int i=20;i>=0;i--)if(f[x][i]!=f[y][i])x=f[x][i],y=f[y][i];
return f[x][0];
}
void dfs(int u,int fa){
dfn[u]=++num,rk[num]=u,siz[u]=1,dep[u]=dep[fa]+1;
f[u][0]=fa;
for(int i=1;i<=20;i++)f[u][i]=f[f[u][i-1]][i-1];
for(auto v:g[u]){
if(v==fa)continue;
dfs(v,u);
siz[u]+=siz[v];
}
}
int fa[N];
inline int get_f(int x){return (fa[x]==0||fa[x]==x)?x:(fa[x]=get_f(fa[x]));}
void _merge(int x,int y){
if(fa[x]){fa[get_f(x)]=y;return;}
fa[x]=y;
for(auto v:g[x]){
if(v==f[x][0])continue;
_merge(v,y);
}
}
void init(){
cin>>n;
for(int i=1;i<=n;i++)g[i].clear();
for(int i=1,u,v;i<n;i++)cin>>u>>v,g[u].push_back(v),g[v].push_back(u);
num=0,dep[0]=-1;
dfs(1,0);
}
//主席树
int T[N],L[N<<5],R[N<<5],sum[N<<5],tot;
void update(int pre,int &now,int l,int r,int x){//插入权值x
now=++tot;L[now]=L[pre],R[now]=R[pre],sum[now]=sum[pre]+1;
if(l==r)return;
int mid=(l+r)>>1;
if(x<=mid)update(L[pre],L[now],l,mid,x);
else update(R[pre],R[now],mid+1,r,x);
}
int query(int pre,int now,int l,int r,int x){//查询l到r区间比x大的有几个
if(x<l)return sum[now]-sum[pre];
else{
int ans=0,mid=(l+r)>>1;
if(x<mid)ans+=query(L[pre],L[now],l,mid,x);
if(x<r)ans+=query(R[pre],R[now],mid+1,r,x);
return ans;
}
}
int dis(int x,int y){
return dep[x]+dep[y]-2*dep[lca(x,y)];
}
int get(int x,int y){
int cx=0,cy=0,fx=x,fy=y;
if(fa[x])fx=get_f(x),cx=query(T[dfn[fx]-1],T[dfn[fx]+siz[fx]-1],1,n,x);
if(fa[y])fy=get_f(y),cy=query(T[dfn[fy]-1],T[dfn[fy]+siz[fy]-1],1,n,y);
if(fx==fy)return abs(cx-cy);
else return dis(fx,fy)+cx+cy;
}
void solve(){
tot=0;
for(int i=1;i<=n;i++)update(T[i-1],T[i],1,n,rk[i]);//dfs序建主席树
for(int i=1;i<=n;i++)fa[i]=0;
cin>>q;
while(q--){
int opt,x,y;
cin>>opt;
if(opt==1){
cin>>x;
if(!fa[x])_merge(x,x);
}else{
cin>>x>>y;
int ans=get(x,y);
cout<<ans<<endl;
}
}
}
int main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>t;
while(t--){
init();
solve();
}
return 0;
}
解2:和解1思路一样,实现方式不一样,合并链可用线段树维护,子树dfs序是一段区间,这段区间赋为链顶即可,还有lca可用树链剖分来求。
代码:
/**
* author: IQ^QI
* created: 26.08.2019
**/
#include<bits/stdc++.h>
#define ls(p) (p<<1)
#define rs(p) (p<<1|1)
using namespace std;
const int N=1e5+9;
int t,n,q;
vector<int>g[N];
int f[N],siz[N],num,son[N],dep[N];//dfs1
int top[N],dfn[N],rk[N];//dfs2
void dfs1(int u,int fa){
siz[u]=1,f[u]=fa;dep[u]=dep[fa]+1;
for(auto v:g[u]){
if(v==fa)continue;
dfs1(v,u);
siz[u]+=siz[v];
if(siz[son[u]]<siz[v])son[u]=v;
}
}
void dfs2(int u,int x){
dfn[u]=++num,rk[num]=u,top[u]=x;
if(son[u])dfs2(son[u],x);
for(auto v:g[u]){
if(v==f[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]])swap(x,y);
x=f[top[x]];
}
if(dep[x]>dep[y])swap(x,y);
return x;
}
void init(){
cin>>n;
for(int i=1;i<=n;i++)g[i].clear();
for(int i=1,u,v;i<n;i++)cin>>u>>v,g[u].push_back(v),g[v].push_back(u);
num=0;memset(son,0,sizeof(son));dep[0]=-1;
dfs1(1,0),dfs2(1,1);
}
int tag[N<<2];
inline void push_down(int p){if(!tag[p])return;tag[ls(p)]=tag[rs(p)]=tag[p];tag[p]=0;}
void update(int p,int l,int r,int ql,int qr,int x){
if(ql<=l&&r<=qr)tag[p]=x;
else{
push_down(p);
int mid=(l+r)>>1;
if(ql<=mid)update(ls(p),l,mid,ql,qr,x);
if(qr>mid)update(rs(p),mid+1,r,ql,qr,x);
}
}
int query(int p,int l,int r,int qx){
if(l==r)return tag[p];
if(tag[p])return tag[p];
else{
push_down(p);
int mid=(l+r)>>1;
if(qx<=mid)return query(ls(p),l,mid,qx);
else return query(rs(p),mid+1,r,qx);
}
}
//主席树
int T[N],L[N<<5],R[N<<5],sum[N<<5],tot;
void upd(int pre,int &now,int l,int r,int x){//插入权值x
now=++tot;L[now]=L[pre],R[now]=R[pre],sum[now]=sum[pre]+1;
if(l==r)return;
int mid=(l+r)>>1;
if(x<=mid)upd(L[pre],L[now],l,mid,x);
else upd(R[pre],R[now],mid+1,r,x);
}
int qry(int pre,int now,int l,int r,int x){//查询区间比x大的个数
if(x<l)return sum[now]-sum[pre];
else{
int ans=0,mid=(l+r)>>1;
if(x<mid)ans+=qry(L[pre],L[now],l,mid,x);
if(x<r)ans+=qry(R[pre],R[now],mid+1,r,x);
return ans;
}
}
inline int dis(int x,int y){return dep[x]+dep[y]-2*dep[lca(x,y)];}
int calc(int x,int y){
int fx=query(1,1,n,dfn[x]),fy=query(1,1,n,dfn[y]),cx=0,cy=0;
if(fx)cx=qry(T[dfn[fx]-1],T[dfn[fx]+siz[fx]-1],1,n,x);
if(fy)cy=qry(T[dfn[fy]-1],T[dfn[fy]+siz[fy]-1],1,n,y);
if(fx&&fx==fy)return abs(cx-cy);
if(!fx)fx=x;if(!fy)fy=y;
return cx+cy+dis(fx,fy);
}
void solve(){
tot=0;
for(int i=1;i<=n;i++)upd(T[i-1],T[i],1,n,rk[i]);
memset(tag,0,sizeof(tag));
cin>>q;
while(q--){
int opt,x,y;
cin>>opt;
if(opt==1){
cin>>x;
int d=query(1,1,n,dfn[x]);
if(!d)update(1,1,n,dfn[x],dfn[x]+siz[x]-1,x);
}else{
cin>>x>>y;
int ans=calc(x,y);
cout<<ans<<endl;
}
}
}
int main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>t;
while(t--){
init();
solve();
}
return 0;
}