【BZOJ】3572: [Hnoi2014]世界树 虚树+倍增

【题意】给定n个点的树,m次询问,每次给定ki个特殊点,一个点会被最近的特殊点控制,询问每个特殊点控制多少点。n,m,Σki<=300000。

【算法】虚树+倍增

【题解】★参考:thy_asdf

对询问建立虚树,然后DFS统计虚树上每个点被哪个点控制,记为belong[x]。

统计的方法是从下往上DFS得到x来自x子树的控制点,再从上往下DFS得到x来自非x子树的控制点。

令D(x,y)表示点x和点y之间的路径长度,比较x的不同控制点y的方式是取D(x,y)的最小值(相同时比较编号)。

 

依此考虑虚树上的每一条边(x,y),记z为同时是x的儿子和y的祖先的点,令f[x]表示点x的答案。

如果belong[x]=belong[y],那么f[belong[x]]+=size[z]-size[y]

否则,从y倍增找到分界点mid,满足mid及以下由y控制,mid以上由x控制。

倍增过程中,记A=D(belong[x],mid),B=D(belong[x],mid),当满足A>B或A=B&&belong[x]>belong[y]时进行倍增。

找到分界点mid后,f[belong[x]]+=size[x]-size[m],f[belong[y]+=size[m]-size[y]。

 

最后要处理没有出现在虚树路径上的点(包括虚树节点),这些点没有在上述过程中被统计。

记rem[x]表示子树x中未被统计的节点数,初始rem[x]=size[x],特别的rem[a[1]]=n(a[1]是虚树最高节点,n是原树总点数,这样是为了保存a[1]往上延伸的节点)

每次处理完边(x,y),rem[x]-=size[z]。

全部做完后,对虚树的每个点f[belong[x]]+=rem[x]。

#include<cstdio>
#include<cctype>
#include<cstring>
#include<algorithm>
using namespace std;
int read(){
    int s=0,t=1;char c;
    while(!isdigit(c=getchar()))if(c=='-')t=-1;
    do{s=s*10+c-'0';}while(isdigit(c=getchar()));
    return s*t;
}
const int maxn=300010;
int n,N,size[maxn],in[maxn],ou[maxn],f[maxn],a[maxn*2],b[maxn];
int deep[maxn],tot,first[maxn],belong[maxn],st[maxn];
int rem[maxn];
bool v[maxn];
namespace cyc{
    int first[maxn],tot,dfsnum=0,f[maxn][30];
    struct edge{int v,from;}e[maxn*2];
    void insert(int u,int v){tot++;e[tot].v=v;e[tot].from=first[u];first[u]=tot;}
    void dfs(int x,int fa){
        in[x]=++dfsnum;size[x]=1;
        for(int j=1;(1<<j)<=deep[x];j++)f[x][j]=f[f[x][j-1]][j-1];
        for(int i=first[x];i;i=e[i].from)if(e[i].v!=fa){
            deep[e[i].v]=deep[x]+1;
            f[e[i].v][0]=x;
            dfs(e[i].v,x);
            size[x]+=size[e[i].v];
        }
        ou[x]=dfsnum;
    }
    int lca(int x,int y){
        if(deep[x]<deep[y])swap(x,y);
        int d=deep[x]-deep[y];
        for(int j=0;j<=20;j++)if(d&(1<<j))x=f[x][j];
        if(x==y)return x;
        for(int j=20;j>=0;j--)if((1<<j)<=deep[x]&&f[x][j]!=f[y][j]){
            x=f[x][j];y=f[y][j];
        }
        return f[x][0];
    }
    void build(){
        n=read();
        for(int i=1;i<n;i++){
            int u=read(),v=read();
            insert(u,v);insert(v,u);
        }
        dfs(1,-1);
    }
}
int D(int x,int y){if(!y)return 0x3f3f3f3f;else return deep[x]+deep[y]-2*deep[cyc::lca(x,y)];}
bool cmp(int a,int b){return in[a]<in[b];}
bool check(int x,int y){return in[x]<=in[y]&&ou[y]<=ou[x];}
struct edge{int u,v,from;}e[maxn*2];
void insert(int u,int v){tot++;e[tot].u=u;e[tot].v=v;e[tot].from=first[u];first[u]=tot;}
void dfs1(int x){
    if(v[x])belong[x]=x;
    for(int i=first[x];i;i=e[i].from){
        dfs1(e[i].v);
        int A=D(x,belong[x]),B=D(x,belong[e[i].v]);
        if(B<A||(B==A&&belong[e[i].v]<belong[x]))belong[x]=belong[e[i].v];
    }
}
void dfs2(int x){
    for(int i=first[x];i;i=e[i].from){
        int A=D(e[i].v,belong[x]),B=D(e[i].v,belong[e[i].v]);
        if(A<B||(A==B&&belong[x]<belong[e[i].v]))belong[e[i].v]=belong[x];
        dfs2(e[i].v);
    }
}    
void solve(int x,int y){
    int z=y;
    for(int i=20;i>=0;i--)if((1<<i)<=deep[z]&&deep[cyc::f[z][i]]>=deep[x]+1)z=cyc::f[z][i];
    rem[x]-=size[z];
    if(belong[x]==belong[y]){f[belong[x]]+=size[z]-size[y];return;}//
    int m=y;
    for(int i=20;i>=0;i--)if((1<<i)<=deep[m]){
        if(deep[cyc::f[m][i]]<deep[x])continue;
        int A=D(cyc::f[m][i],belong[x]),B=D(cyc::f[m][i],belong[y]);
        if(B<A||(A==B&&belong[x]>belong[y]))m=cyc::f[m][i];//
    }
    f[belong[x]]+=size[z]-size[m];
    f[belong[y]]+=size[m]-size[y];
}
void work(){
    int last=read();N=last;
    for(int i=1;i<=N;i++)a[i]=read(),f[b[i]=a[i]]=0,v[a[i]]=1;
    sort(a+1,a+N+1,cmp);
    for(int i=1;i<last;i++)a[++N]=cyc::lca(a[i],a[i+1]);
    sort(a+1,a+N+1,cmp);
    N=unique(a+1,a+N+1)-a-1;
    tot=0;
    for(int i=1;i<=N;i++)first[a[i]]=0,belong[a[i]]=0,rem[a[i]]=size[a[i]];
    rem[a[1]]=n;
    int top=0;
    for(int i=1;i<=N;i++){
        while(top&&!check(st[top],a[i]))top--;
        if(top)insert(st[top],a[i]);
        st[++top]=a[i];
    }
    dfs1(a[1]);dfs2(a[1]);
    for(int i=1;i<=tot;i++)solve(e[i].u,e[i].v);
    for(int i=1;i<=N;i++)f[belong[a[i]]]+=rem[a[i]];
    for(int i=1;i<=last;i++)printf("%d ",f[b[i]]),v[b[i]]=0;
}
int main(){
    cyc::build();
    int m=read();
    while(m--)work();
    return 0;
}
View Code

 (倍增求LCA)

 

 

 

注意,由于多次调用LCA,所以用倍增LCA算法有很大的常数,甚至在LOJ会导致TLE。

但是倍增求LCA会使过程更简洁。

下面这份代码是树链剖分求LCA,代码可读性不高。

#include<cstdio>
#include<cctype>
#include<cstring>
#include<algorithm>
using namespace std;
int read(){
    int s=0,t=1;char c;
    while(!isdigit(c=getchar()))if(c=='-')t=-1;
    do{s=s*10+c-'0';}while(isdigit(c=getchar()));
    return s*t;
}
const int maxn=300010;
int n,N,size[maxn],in[maxn],ou[maxn],f[maxn],a[maxn*2],b[maxn];
int deep[maxn],tot,first[maxn],belong[maxn],st[maxn];
int rem[maxn];
bool v[maxn];
namespace cyc{
    int first[maxn],tot,dfsnum=0,f[maxn][30];
    struct edge{int v,from;}e[maxn*2];
    void insert(int u,int v){tot++;e[tot].v=v;e[tot].from=first[u];first[u]=tot;}
    void dfs(int x,int fa){
        in[x]=++dfsnum;size[x]=1;
        for(int j=1;(1<<j)<=deep[x];j++)f[x][j]=f[f[x][j-1]][j-1];
        for(int i=first[x];i;i=e[i].from)if(e[i].v!=fa){
            deep[e[i].v]=deep[x]+1;
            f[e[i].v][0]=x;
            dfs(e[i].v,x);
            size[x]+=size[e[i].v];
        }
        ou[x]=dfsnum;
    }
    int lca(int x,int y){
        if(deep[x]<deep[y])swap(x,y);
        int d=deep[x]-deep[y];
        for(int j=0;j<=20;j++)if(d&(1<<j))x=f[x][j];
        if(x==y)return x;
        for(int j=20;j>=0;j--)if((1<<j)<=deep[x]&&f[x][j]!=f[y][j]){
            x=f[x][j];y=f[y][j];
        }
        return f[x][0];
    }
    void build(){
        n=read();
        for(int i=1;i<n;i++){
            int u=read(),v=read();
            insert(u,v);insert(v,u);
        }
        dfs(1,-1);
    }
}
int D(int x,int y){if(!y)return 0x3f3f3f3f;else return deep[x]+deep[y]-2*deep[cyc::lca(x,y)];}
bool cmp(int a,int b){return in[a]<in[b];}
bool check(int x,int y){return in[x]<=in[y]&&ou[y]<=ou[x];}
struct edge{int u,v,from;}e[maxn*2];
void insert(int u,int v){tot++;e[tot].u=u;e[tot].v=v;e[tot].from=first[u];first[u]=tot;}
void dfs1(int x){
    if(v[x])belong[x]=x;
    for(int i=first[x];i;i=e[i].from){
        dfs1(e[i].v);
        int A=D(x,belong[x]),B=D(x,belong[e[i].v]);
        if(B<A||(B==A&&belong[e[i].v]<belong[x]))belong[x]=belong[e[i].v];
    }
}
void dfs2(int x){
    for(int i=first[x];i;i=e[i].from){
        int A=D(e[i].v,belong[x]),B=D(e[i].v,belong[e[i].v]);
        if(A<B||(A==B&&belong[x]<belong[e[i].v]))belong[e[i].v]=belong[x];
        dfs2(e[i].v);
    }
}    
void solve(int x,int y){
    int z=y;
    for(int i=20;i>=0;i--)if((1<<i)<=deep[z]&&deep[cyc::f[z][i]]>=deep[x]+1)z=cyc::f[z][i];
    rem[x]-=size[z];
    if(belong[x]==belong[y]){f[belong[x]]+=size[z]-size[y];return;}//
    int m=y;
    for(int i=20;i>=0;i--)if((1<<i)<=deep[m]){
        if(deep[cyc::f[m][i]]<deep[x])continue;
        int A=D(cyc::f[m][i],belong[x]),B=D(cyc::f[m][i],belong[y]);
        if(B<A||(A==B&&belong[x]>belong[y]))m=cyc::f[m][i];//
    }
    f[belong[x]]+=size[z]-size[m];
    f[belong[y]]+=size[m]-size[y];
}
void work(){
    int last=read();N=last;
    for(int i=1;i<=N;i++)a[i]=read(),f[b[i]=a[i]]=0,v[a[i]]=1;
    sort(a+1,a+N+1,cmp);
    for(int i=1;i<last;i++)a[++N]=cyc::lca(a[i],a[i+1]);
    sort(a+1,a+N+1,cmp);
    N=unique(a+1,a+N+1)-a-1;
    tot=0;
    for(int i=1;i<=N;i++)first[a[i]]=0,belong[a[i]]=0,rem[a[i]]=size[a[i]];
    rem[a[1]]=n;
    int top=0;
    for(int i=1;i<=N;i++){
        while(top&&!check(st[top],a[i]))top--;
        if(top)insert(st[top],a[i]);
        st[++top]=a[i];
    }
    dfs1(a[1]);dfs2(a[1]);
    for(int i=1;i<=tot;i++)solve(e[i].u,e[i].v);
    for(int i=1;i<=N;i++)f[belong[a[i]]]+=rem[a[i]];
    for(int i=1;i<=last;i++)printf("%d ",f[b[i]]),v[b[i]]=0;
}
int main(){
    cyc::build();
    int m=read();
    while(m--)work();
    return 0;
}
View Code

 

upd:复习一下。

【建虚树】

第一步、所有关键点按DFS序排序,加入LCA后排序去重。

第二步、按顺序用栈维护构造出虚树。

第三步、虚树DP后清零。

【此题的虚树DP】

第一步、处理虚树上点与点。

第二步、处理虚树边上点的子树。

第三步、处理虚树点的 [ 不在虚树上的孩子 ] 子树。(通过减虚树孩子的sz得到)

posted @ 2018-02-27 20:30  ONION_CYC  阅读(258)  评论(0编辑  收藏  举报