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

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值