zoukankan      html  css  js  c++  java
  • [洛谷P3233] HNOI2014 世界树

    问题描述

    世界树是一棵无比巨大的树,它伸出的枝干构成了整个世界。在这里,生存着各种各样的种族和生灵,他们共同信奉着绝对公正公平的女神艾莉森,在他们的信条里,公平是使世界树能够生生不息、持续运转的根本基石。
    世界树的形态可以用一个数学模型来描述:世界树中有n个种族,种族的编号分别从1到n,分别生活在编号为1到n的聚居地上,种族的编号与其聚居地的编号相同。有的聚居地之间有双向的道路相连,道路的长度为1。保证连接的方式会形成一棵树结构,即所有的聚居地之间可以互相到达,并且不会出现环。定义两个聚居地之间的距离为连接他们的道路的长度;例如,若聚居地a和b之间有道路,b和c之间有道路,因为每条道路长度为1而且又不可能出现环,所卧a与c之间的距离为2。
    出于对公平的考虑,第i年,世界树的国王需要授权m[i]个种族的聚居地为临时议事处。对于某个种族x(x为种族的编号),如果距离该种族最近的临时议事处为y(y为议事处所在聚居地的编号),则种族x将接受y议事处的管辖(如果有多个临时议事处到该聚居地的距离一样,则y为其中编号最小的临时议事处)。
    现在国王想知道,在q年的时间里,每一年完成授权后,当年每个临时议事处将会管理多少个种族(议事处所在的聚居地也将接受该议事处管理)。 现在这个任务交给了以智慧著称的灵长类的你:程序猿。请帮国王完成这个任务吧。

    输入格式

    第一行为一个正整数n,表示世界树中种族的个数。
    接下来n-l行,每行两个正整数x,y,表示x聚居地与y聚居地之间有一条长度为1的双
    向道路。接下来一行为一个正整数q,表示国王询问的年数。
    接下来q块,每块两行:
    第i块的第一行为1个正整数m[i],表示第i年授权的临时议事处的个数。
    第i块的第二行为m[i]个正整数h[l]、h[2]、…、h[m[i]],表示被授权为临时议事处的聚居地编号(保证互不相同)。

    输出格式

    输出包含q行,第i行为m[i]个整数,该行的第j(j=1,2…,,m[i])个数表示第i年被授权的聚居地h[j]的临时议事处管理的种族个数。

    样例输入

    10
    2 1
    3 2
    4 3
    5 4
    6 1
    7 3
    8 3
    9 4
    10 1
    5
    2
    6 1
    5
    2 7 3 6 9
    1
    8
    4
    8 7 10 3
    5c
    2 9 3 5 8

    样例输出

    1 9
    3 1 4 1 1
    10
    1 1 3 5
    4 1 3 1 1

    解析

    首先,我们来分析一下这道题的模型。有q个询问,每次有m个关键点,而(sum{m[i]})又比较小,我们由此可以想到虚树。那么以每个临时议事处为关键点,建立一棵虚树。

    虚树建立好后,我们就要考虑如何在上面进行动态规划。要求虚树上离一个点距离最近的关键点,显然,距离一个关键点最近的关键点就是它本身。那么对于保留的其他点,设(to[i])表示(i)归哪个点管辖,用树上动规的思想,显然一个点的(to)值要么由它的儿子更新(即儿子所属的点到该点的距离更近或编号更小),要么由它的父亲更新。由此,我们可以分两次动态规划,一次用儿子去更新父亲,一次用父亲更新儿子。根据更新顺序不同,DFS的先后也不同。

    之后,我们就要求每个议事处管辖多少点了。目前已知的信息有虚树上的关键点所属的议事处,我们接下来的任务是用这些信息得到树上的其他节点的信息。再来回忆虚树的构建原理,两个相邻关键点之间本来就收缩了一条链,现在要把这条链复原出来并统计答案。对于虚树上两相邻点(a b)之间的链,必然存在一个分界点(c)使(c)以下的点归(to[b])管辖,以上的归(to[a])管辖。容易想到,这个分界点必然是(to[a])(to[b])之间的中点(当然中点必然在(a)(b)之间,否则(ab)一定归属于同一个点)。我们不能直接一个一个地找,可以用倍增的方法。那么怎么求链((a,b))之间的贡献呢?设(a)的第一个儿子为(x),由于链上一定没有议事处(虚树......),从(b)(c)之间的点及其子树都属于(to[b]),其余属于(to[a])。用算式表达即为:

    [f[to[a]]+=size[x]-size[c],f[to[b]]+=size[c]-size[b] ]

    其中(f[i])表示(i)点管辖的节点数量,(size[i])表示根节点为(i)的子树大小。

    可以看出,以上过程中没有讨论虚树上的关键点产生的贡献。这些点的特殊之处在于他们可能会连接其他的议事处。对于任意虚树关键点(u),只有没有议事处的子树才能直接判断为和(u)点一起归(to[u])管辖,其余则已在之前的计算中统计了。因此,设(rest[u])初始化为(size[u]),在上面的过程中,每找到一个关键点(a)(x),就使(rest[a]-=size[x]),最后单独将(rest)累加入答案中。

    讲了这么久,终于可以上代码了————

    代码

    // luogu-judger-enable-o2
    #include <iostream>
    #include <cstdio>
    #include <cmath>
    #include <cstring>
    #include <algorithm>
    #define N 300002
    using namespace std;
    int head[N],ver[N*2],nxt[N*2],l;
    int n,m,i,j,k,size[N],dep[N],fa[N][20],tim,dfn[N],x;
    int a[N],tmp[N],to[N],s[N],t[N],top,cnt,rec[N],rest[N],f[N];
    int read()
    {
        char c=getchar();
        int w=0;
        while(c<'0'||c>'9') c=getchar();
        while(c<='9'&&c>='0'){
            w=w*10+c-'0';
            c=getchar();
        }
        return w;
    }
    void insert(int x,int y)
    {
        l++;
        ver[l]=y;
        nxt[l]=head[x];
        head[x]=l;
    }
    void dfs(int x,int pre)
    {
        dfn[x]=++tim;fa[x][0]=pre;
        size[x]=1;dep[x]=dep[pre]+1;
        for(int i=head[x];i;i=nxt[i]){
            int y=ver[i];
            if(y!=pre){
                dfs(y,x);
                size[x]+=size[y];
            }
        }
    }
    void init()
    {
        dfs(1,0);
        for(int j=0;(1<<(j+1))<n;j++){
            for(int i=1;i<=n;i++){
                if(fa[i][j]==0) fa[i][j+1]=0;
                else fa[i][j+1]=fa[fa[i][j]][j];
            }
        }
    }
    int LCA(int u,int v)
    {
        if(dep[u]>dep[v]) swap(u,v);
        int tmp=dep[v]-dep[u];
        for(int i=0;(1<<i)<=tmp;i++){
            if(tmp&(1<<i)) v=fa[v][i];
        }
        if(u==v) return u;
        for(int i=(int)log2(1.0*n);i>=0;i--){
            if(fa[u][i]!=fa[v][i]) u=fa[u][i],v=fa[v][i];
        }
        return fa[u][0];
    }
    int cmp(const int &x,const int &y)
    {
        return dfn[x]<dfn[y];
    }
    int dis(int x,int y)
    {
        return dep[x]+dep[y]-2*dep[LCA(x,y)];
    }
    void dfs1(int x)
    {
        rec[++tim]=x;rest[x]=size[x];
        for(int i=head[x];i;i=nxt[i]){
            int y=ver[i];
            dfs1(y);
            if(!to[y]) continue;
            int d1=dis(to[y],x),d2=dis(to[x],x);
            if(d1<d2||(d1==d2&&to[y]<to[x])||!to[x]) to[x]=to[y];
        }
    }
    void dfs2(int x)
    {
        for(int i=head[x];i;i=nxt[i]){
            int y=ver[i];
            int d1=dis(to[x],y),d2=dis(to[y],y);
            if(d1<d2||(d1==d2&&to[x]<to[y])||!to[y]) to[y]=to[x];
            dfs2(y);
        }
    }
    void dp(int a,int b)
    {
        int x=b,mid=b;
        for(int i=18;i>=0;i--){
            if(dep[fa[x][i]]>dep[a]) x=fa[x][i];
        }
        rest[a]-=size[x];
        if(to[a]==to[b]){
            f[to[a]]+=size[x]-size[b];
            return;
        }
        for(int i=18;i>=0;i--){
            int nxt=fa[mid][i];
            if(dep[nxt]<=dep[a]) continue;
            int d1=dis(nxt,to[b]),d2=dis(nxt,to[a]);
            if(d1<d2||(d1==d2&&to[b]<to[a])) mid=nxt;
        }
        f[to[a]]+=size[x]-size[mid];
        f[to[b]]+=size[mid]-size[b];
    }
    int main()
    {
        n=read();
        for(i=1;i<n;i++){
            int u,v;
            u=read();v=read();
            insert(u,v);insert(v,u);
        }
        init();
        m=read();
        for(i=1;i<=m;i++){
            memset(head,0,sizeof(head));
            memset(f,0,sizeof(f));
            memset(to,0,sizeof(to));
            memset(rest,0,sizeof(rest));
            tim=top=l=0;
            x=read();
            for(j=1;j<=x;j++){
                a[j]=read();
                to[a[j]]=tmp[j]=a[j];
            }
            sort(a+1,a+x+1,cmp);
            if(to[1]!=1) s[++top]=1;
            for(j=1;j<=x;j++){
                int lca=0;
                while(top){
                    lca=LCA(a[j],s[top]);
                    if(top>1&&dep[s[top-1]]>dep[lca]){
                        insert(s[top-1],s[top]);
                        top--;
                    }
                    else if(dep[s[top]]>dep[lca]){
                        insert(lca,s[top]);
                        top--;
                        break;
                    }
                    else break;
                }
                if(s[top]!=lca) s[++top]=lca;
                s[++top]=a[j];
            }
            while(top>1){
                insert(s[top-1],s[top]);
                top--;
            }
            dfs1(1);dfs2(1);
            for(j=1;j<=tim;j++){
                for(k=head[rec[j]];k;k=nxt[k]) dp(rec[j],ver[k]);
            }
            for(j=1;j<=tim;j++) f[to[rec[j]]]+=rest[rec[j]];
            for(j=1;j<=x;j++) printf("%d ",f[tmp[j]]);
            puts("");
        }
        return 0;
    }
    //P.S. 用轻重链剖分求LCA可以不用开O2。(O_O)
    
  • 相关阅读:
    Mysql:Changes in MySQL 5.6.6 (2012-08-07, Milestone 9):Group-Commit等等:重大变化版本!
    Mysql:Changes in MySQL 5.6.9 (2012-12-11, Release Candidate):GTID-based variables have been 【renamed】
    Mysql:Changes in MySQL 5.6.13 (2013-07-31, General Availability):不再支持可能引起混乱的【选项缩略】写法!
    Mysql:Changes in MySQL 5.6.22 (2014-12-01, General Availability):【sql-log-bin】
    Mysql:Changes in MySQL 5.6.30 (2016-04-11, General Availability):--ssl-mode:
    Mysql:Changes in MySQL 5.6.34 (2016-10-12, General Availability):secure-file-priv
    Windows Linux子系统Windows 10安装指南
    WSL2-参考的对象类型不支持尝试的操作。
    Win10开启Hyper-V后无法运行VMware虚拟机的解决方法
    Kubernetes---高可用的 K8S 集群构建
  • 原文地址:https://www.cnblogs.com/LSlzf/p/10985732.html
Copyright © 2011-2022 走看看