题解 P5180 【【模板】支配树】

· · 题解

蒟蒻的题解

题目分析: 首先肯定是个模板题啦,一个有向图,给你一个起点,求每个点的支配的点的个数

支配点: 从u到v的所有路径如果都经过一个点p,那么我们称这个点p为v的支配点,如果把p切掉,那么u将无法到达v,显然一个点的支配点可以有很多个(比如:) 对于这个图 从0出发,7的支配点有3和6

半支配点: 存在从一个点u到v的路径中(不包括u,v),所有的点pi的dfn都大于v的dfn(下面两个点的比较大小都指的是他们的dfn)(我们在做题前是要扣出一个dfs树的),当然如果u是v在dfs树上的父节点,那么u也是v的半支配点

为什么要求支配点: 因为我们是做不到直接求支配点的,支配点一定是半支配点,所以我们要找到所有点的半支配点,然后去求支配点,所有半支配点都有机会成为支配点,我们用semi[v]数组来表示v点的所有半支配点的最小的那个

原理: 具体原理其实很多大佬已经讲得很详细了,欢迎看完了他们的讲解再来看我的题解,然后我帮你水过这个题(因为他们讲的太过详细(所以就不一定能够完全看的懂))(最好还是先去他们那里水个几个小时) 因为这里的小蒟蒻 并不会讲太多的原理 基本没有

基本思路:

上代码:

# include <iostream>
# include <cstdio>
# include <cstring>
# include <queue>
# define sz 20
# define FOR(i,st,ed) for(int i=st;i<=ed;++i)
# define _FOR(tu,u) for(int v,i=tu.hd[u];i;i=tu.e[i].nt)
# define _(tu) v=tu.e[i].to;
using namespace std;
int re(){
    int s=0,f=1;char ch=getchar();
    while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
    while(isdigit(ch)){s=(s<<1)+(s<<3)+ch-'0';ch=getchar();}
    return s*f;
}//快读就不解释了

const int N=2e5+7;
struct MAP{
    struct edge{
        int to,nt;
    }e[N<<1];int cnt,hd[N];
    void link(int x,int y){e[++cnt]=(edge){y,hd[x]};hd[x]=cnt;}
}xx,a,ra,t,rt,z;//排除xx,依次表示原图,原图的反图,dfs树
//dfs树的反图,和最后的支配树
int n,m;

int dfn[N],dji,id[N],ff[N];//ff表示dfs树上的父节点
void dfs(int u){
    id[dfn[u]=++dji]=u;
    _FOR(a,u){
        _(a);
        if(!dfn[v]) dfs(v),ff[v]=u,t.link(u,v); 
    }
}//扣出一个dfs树

int anc[N],semi[N],mn[N];//这个mn[v],表示v点的dfs树上的祖先
//的semi最小的那个祖先,所以semi[mn[x]]=semi[x];
void init(){
    FOR(i,1,n) anc[i]=semi[i]=mn[i]=i;
}
int find(int u){
    if(u!=anc[u]){
        int t=anc[u];anc[u]=find(anc[u]);
        if(dfn[semi[mn[u]]]>dfn[semi[mn[t]]]) mn[u]=mn[t];
    }
    return anc[u];
}//以上是带权并查集(路径压缩)
void len_tarjan(){
    init();
    for(int j=n;j>=2;--j){
        int u=id[j],res=j;
        if(!u) continue;
        _FOR(ra,u){
            _(ra);
            if(!dfn[v]) continue;
            if(dfn[v]<dfn[u]) res=min(res,dfn[v]);
            else find(v),res=min(res,dfn[semi[mn[v]]]);
        }
        semi[u]=id[res];anc[u]=ff[u];t.link(semi[u],u); 
    }
}//我们的tarjan大佬的算法

int ru[N],dep[N],fa[N][25];
int lca(int x,int y){
    if(dep[x]<dep[y]) swap(x,y);
    int del=dep[x]-dep[y];
    FOR(i,0,sz)
        if((1<<i)&del) x=fa[x][i];
    if(x==y) return x;
    for(int i=sz;i>=0;--i)
        if(fa[x][i]!=fa[y][i]) x=fa[x][i],y=fa[y][i];
    return fa[x][0];
}/lca
void work(int u){
    int t=rt.e[rt.hd[u]].to;
    _FOR(rt,u){
        _(rt);t=lca(t,v);
    }
    dep[u]=dep[t]+1;
    fa[u][0]=t;
    z.link(t,u);
    FOR(i,1,sz)
        fa[u][i]=fa[fa[u][i-1]][i-1]; 
}//建立支配树
void tpsort(){
    FOR(j,1,n)
        _FOR(t,j){
            _(t);
            ru[v]++;rt.link(v,j); 
        }   
    FOR(i,1,n) if(!ru[i]) t.link(0,i),rt.link(i,0);
    queue <int> q;q.push(0);
    while(q.size()){
        int u=q.front();q.pop();
        _FOR(t,u){
            _(t);
            if((--ru[v])<=0) q.push(v),work(v);
        }
    } 
}

int siz[N];
void adfs(int u){
    siz[u]=1;
    _FOR(z,u){
        _(z);adfs(v);siz[u]+=siz[v];
    }
}//最后adfs用来搜答案
int main(){
    n=re();m=re();int x,y;
    FOR(i,1,m){
        x=re();y=re();
        a.link(x,y);ra.link(y,x);  
    }
    dfs(1);
    len_tarjan();
    tpsort();
    adfs(0);
    FOR(i,1,n) cout<<siz[i]<<' ';
    return 0;
}//好的 我们成功的水了一道黑题!