世界树(HNOI2014)

世界树(HNOI2014)

题目描述

给出一棵有\(n\)个点,边权全部为\(1\)的树,有\(q\)个询问。

对于每个询问,都会给出树中的\(m\)个点,对于所有的\(n\)个点,每个点都会附属于这\(m\)个点中距离它本身最近的点(若满足条件,可以附属于自身上),要求你求出对于这\(m​\)个点分别有多少个节点附属于它。

\(\sum{m}<=300000\)

思路

这题其实(据说)用虚树写蛮简单,但由于本人较弱不会,讲一个\(dfs序+线段树+倍增LCA+离线\)的方法。。。

首先是预处理倍增,LCA,\(dfs\)序等,这些不多说。接下来我们做这样的操作:把每次询问的\(m\)个点按照点的深度排序。如果按此序依次放入每一个点,就不可能在该点的子树中存在之前放入的点。至于这个小性质有啥用,我们待会在说。。

我们考虑放入一个点后,对那些点的附属值产生影响。这时排序的作用就来了:在这点变成可附属点时,它原本就会有一个它的附属点,那么这样排序就只会影响该点的附属值,说简单点就是:当\(u\)原本附属于\(v\)时,当\(u\)变为可附属点时,只会影响\(v\)的附属值。如何证明应该不用赘述吧!应该画个图就可以理解了,注意是按深度排序的就行了!

接下来就是\(u\)会抢走\(v\)的几个点呢?同理,通过画图以及人类智慧可知,正是从\(u\)\(v\)的路径的中心点\(w\)的子树,由于\(u\)深度大于\(v\)深度,所以\(w\)肯定是\(u\)的祖先,所以就要用倍增跳\(u\),跳到\(w\),然而注意,如果\(w\)\(u\),\(v\)的路径相等时,还要特判两者的编号大小并以此决定向上跳的路径。路径长度便需要\(LCA\)求解。

最后就是如何维护每个点的附属点,这个就不复杂了,由于每次抢走的就是一个子树,所以只要\(dfs\)序区间更新在线段树上即可,若是第一个点(即一开始没可附属点时),就把所有的点(即1~n区间)更新了。

这样综合了之后,这题便得到圆满解决,还有什么细节详见代码。

代码

#include<bits/stdc++.h>
#define FOR(i,l,r) for(int i=l,i##R=r;i<=i##R;i++)
#define DOR(i,r,l) for(int i=r,i##L=l;i>=i##L;i--)
#define loop(i,n) for(int i=0,i##R=n;i<i##R;i++)
#define sf scanf
#define pf printf
#define mms(a,x) memset(a,x,sizeof a)
using namespace std;
typedef long long ll;
typedef long double db;
template<typename A,typename B>inline void chkmax(A &x,const B y){if(x<y)x=y;}
template<typename A,typename B>inline void chkmin(A &x,const B y){if(x>y)x=y;}
const int N=3e5+5;
int n,m,q;
struct Graph{//正向表
    int tot,to[N<<1],nxt[N<<1],head[N];
    void add(int x,int y){tot++;to[tot]=y;nxt[tot]=head[x];head[x]=tot;}
    void clear(){mms(head,-1);tot=0;}
    #define EOR(G,i,x) for(int i=G.head[x];i!=-1;i=G.nxt[i])
}G;
int cnt[N];
int ans[N];
struct node{
    int x,id;
}A[N];
int ct,sz[N],top[N],son[N],dep[N],fa[N][20];
int dfn[N],post[N];
bool cmp(node a,node b){//先按深度排,再按编号排,不能忽略编号
    if(dep[a.x]!=dep[b.x])return dep[a.x]<dep[b.x];
    return a.x<b.x;
}
struct Pt2{
    struct YD_Tree{//维护每个点的附属点
        #define ls (p<<1)
        #define rs (p<<1|1)
        static const int M=(N<<2);
        int bel[M];
        void down(int p){
            if(!bel[p])return;
            bel[ls]=bel[rs]=bel[p];
            bel[p]=0;
        }
        void update(int p,int l,int r,int L,int R,int x){
            if(L<=l&&r<=R){
                bel[p]=x;
                return;
            }
            down(p);
            int mid=(l+r)>>1;
            if(mid<R)update(rs,mid+1,r,L,R,x);
            if(mid>=L)update(ls,l,mid,L,R,x);
        }
        int query(int p,int l,int r,int pos){
            if(bel[p])return bel[p];
            int mid=(l+r)>>1;
            if(mid<pos)return query(rs,mid+1,r,pos);
            else return query(ls,l,mid,pos);
        }
    }Tr;
    void dfs(int x,int f){
        fa[x][0]=f;
        dfn[x]=++ct;
        dep[x]=dep[f]+1;
        sz[x]=1,son[x]=0;
        EOR(G,i,x){
            int v=G.to[i];
            if(v==f)continue;
            dfs(v,x);
            sz[x]+=sz[v];
            if(sz[son[x]]<sz[v])son[x]=v;
        }
        post[x]=ct;
    }
    void top_dfs(int x,int f,int tp){
        top[x]=tp;
        if(son[x])top_dfs(son[x],x,tp);
        EOR(G,i,x){
            int v=G.to[i];
            if(v==f||v==son[x])continue;
            top_dfs(v,x,v);
        }
    }
    int LCA(int a,int b){//个人用了跳重链,常数较小,嫌烦的直接倍增完全没问题
        while(top[a]!=top[b]){
            if(dep[top[a]]>dep[top[b]])a=fa[top[a]][0];
            else b=fa[top[b]][0];
        }
        return dep[a]>dep[b]?b:a;
    }
    void jump(int &x,int len){//倍增跳点
        FOR(i,0,19)
            if((len>>i)&1)x=fa[x][i];
    }
    void solve(){
        sf("%d",&q);
        dfs(1,0);
        top_dfs(1,0,1);
        FOR(j,1,19)FOR(i,1,n)
            fa[i][j]=fa[fa[i][j-1]][j-1];
        while(q--){
            sf("%d",&m);
            FOR(i,1,m){
                sf("%d",&A[i].x);
                A[i].id=i;
                ans[i]=0,cnt[A[i].x]=0;
            }
            sort(A+1,A+m+1,cmp);
            Tr.update(1,1,n,1,n,A[1].x);//第一个点放入时,所有点都附属于它
            cnt[A[1].x]=n;
            FOR(i,2,m){
                int x=A[i].x;
                int last=Tr.query(1,1,n,dfn[x]);//原附属点
                int lca=LCA(x,last);
                int dis=dep[last]+dep[x]-(dep[lca]<<1);
                jump(x,(dis-(x>last))>>1);
                // pf("after jumped:%d sz:%d\n",x,sz[x]);
                cnt[A[i].x]+=sz[x];
                cnt[last]-=sz[x];
                Tr.update(1,1,n,dfn[x],post[x],A[i].x);
            }
            FOR(i,1,m)ans[A[i].id]=cnt[A[i].x];
            FOR(i,1,m)pf("%d ",ans[i]);puts("");
        }
    }
}Pt_2;
int main(){
    freopen("worldtree.in","r",stdin);
    freopen("worldtree.out","w",stdout);
    G.clear();
    sf("%d",&n);
    FOR(i,1,n-1){
        int x,y;
        sf("%d%d",&x,&y);
        G.add(x,y),G.add(y,x);
    }
    Pt_2.solve();
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/Heinz/p/10732398.html