2019 年百度之星·程序设计大赛 - 初赛四

                                        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
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;
}

 

你可能感兴趣的:(2019 年百度之星·程序设计大赛 - 初赛四)