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
using namespace std;
typedef long long ll;
const int N=1e5+9;
int t,n,q;
vectorg[N];
int dfn[N],rk[N],num,siz[N],f[N][21],dep[N];
int lca(int x,int y){
if(dep[x]=0;i--)if(d&(1<=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>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>1;
if(x>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<>t;
while(t--){
init();
solve();
}
return 0;
}
解2:和解1思路一样,实现方式不一样,合并链可用线段树维护,子树dfs序是一段区间,这段区间赋为链顶即可,还有lca可用树链剖分来求。
代码:
/**
* author: IQ^QI
* created: 26.08.2019
**/
#include
#define ls(p) (p<<1)
#define rs(p) (p<<1|1)
using namespace std;
const int N=1e5+9;
int t,n,q;
vectorg[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]]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>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>1;
if(x>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<>t;
while(t--){
init();
solve();
}
return 0;
}