支配树 / Dominator_Tree(待补)

支配树的概念在一些算法中颇为重要,但网上的解释常有模糊之处。本文旨在通过HDU 4694和6604等题目,探讨支配树的性质,并提供模板代码。支配树的生成可通过拓扑排序结合最近公共祖先(LCA)方法,也可利用Lengauer-Tarjan算法。虽然理解难度较大,但掌握后对于解决特定类型问题非常有用。

参考博客0
参考博客1
半支配点
在这里插入图片描述
支配树的生成方法实在难懂,网上博客多少有点表述不清。暂时先只掌握支配树的性质,存下求树的模板,日后再补上求树过程。

HDU 4694
模板题,ksf代码初始化有点问题,AC代码:

#include <cstdio>
#include <algorithm>
#include <iostream>

typedef long long ll ;
using namespace std;
//DT.init(n) -> DT.add(u,v) -> DT.work(root);
//DT.idom[i] : i 的最近支配点(root 到 i 距离 i 最近的必经点)
//需要完整的支配树用 idom[i]作为 fa[i]建树即可
const int N = 50005, M = 100005; //N 点数,M 主函数中调用 add 的次数
struct Dominator_Tree{
    private: struct eg{ int v, next; }e[M*2+N];
    int tot, cnt, h[N], iw[N], li[N], fa[N], fo[N], vo[N], sdom[N], pre[N], bkt[N];
    inline void link(int h[],int u,int v){ e[++cnt] = {v,h[u]}, h[u] = cnt; }
    inline int eval(int p){ findf(p); return vo[p]; }
    int findf(int p){
        if(fo[p] == p) return p;
        int r = findf(fo[p]);
        if(sdom[vo[fo[p]]] < sdom[vo[p]]) vo[p] = vo[fo[p]];
        return fo[p] = r;
    }
    void dfs(int p){
        sdom[p] = iw[p] = ++tot, li[tot] = p;
        for(int i = h[p]; i; i = e[i].next)
        if(!iw[e[i].v]) dfs(e[i].v), fa[e[i].v] = p;
    }
    public: int idom[N];
    inline void add(int u, int v) { link(h, u, v), link(pre, v, u);}
    inline void work(int root){
        dfs(root); int p, fp;
        for(int j = tot; j >= 2; j--, bkt[fp] = 0){
            for(int i = pre[p=li[j]]; i; i=e[i].next)
                if(iw[e[i].v]) sdom[p] = min(sdom[p], sdom[eval(e[i].v)]);
            link(bkt, li[sdom[p]], p);
            for(int i = bkt[fp=fo[p]=fa[p]]; i; i=e[i].next){
                int u = eval(e[i].v);
                idom[e[i].v] = sdom[u] == sdom[e[i].v] ? fp : u;
            }
        }
        for(int i = 2; i <= tot; i++){
            p = li[i], sdom[p] = li[sdom[p]];
            if(idom[p] != sdom[p]) idom[p] = idom[idom[p]];
        }
    }
    inline void init(int n){
        tot = cnt = 0;
        for (int i = 0; i <= n; i++){
        	pre[i] = bkt[i] = h[i] = iw[i] = 0, fo[i] = vo[i] = i;
        	sdom[i] = idom[i] = 0; //原模版没有这一行
        }
        
    }
}DT;

vector<int> edge[N];
ll ans[N];

void dfs(int now,ll sum)
{
    sum+=now;
    ans[now]=sum;
    for(auto v:edge[now])
    {
        dfs(v,sum);
    }
}
int main()
{
    int n,m;
    while(~scanf("%d%d",&n,&m))
    {
        DT.init(n);
        for(int i=1;i<=m;i++)
        {
            int u,v;
            scanf("%d%d",&u,&v);
            DT.add(u,v);
        }
        DT.work(n);
        for(int i=1;i<=n;i++)
        {
            ans[i]=0;
            edge[i].clear();
        }
        for(int i=1;i<n;i++)
        {
            edge[DT.idom[i]].push_back(i);
        }
        
        dfs(n,0);
        for(int i=1;i<=n;i++)
        {
            if(i>1)printf(" ");
            printf("%lld",ans[i]);
        }
        puts("");
    }
    return 0;
} 

HDU 6604
DAG还通过拓扑排序+LCA来生成支配树:
求出DAG的拓扑序后,顺序遍历拓扑序上的点;假设此时遍历到了点u,u点的最近支配点就是能到u的所有点的LCA。
GJC师兄题解 拓扑+LCA
也能用Lengauer Tarjan求出支配树后对支配树做LCA。这道题因为用tarjan离线做LCA写丑了爆T27发。。。
代码
纯套板。套了ksf的Lengauer Tarjan板子+网上随手抄的树上LCA板子

#include <bits/stdc++.h>

typedef long long ll ;
using namespace std;

inline void read(int & k)
{
	k = 0;
	int f= 1;char ch = getchar();
	while(!isdigit(ch) ){
		if(ch=='-')f =-1;
		ch = getchar();
	}
	while(isdigit(ch)){
		k = (k<<3) + (k<<1) + (ch^'0') ;
		ch = getchar();
	}
	k=k*f;
}

//DT.init(n) -> DT.add(u,v) -> DT.work(root);
//DT.idom[i] : i 的最近支配点(root 到 i 距离 i 最近的必经点)
//需要完整的支配树用 idom[i]作为 fa[i]建树即可
const int N = 200005, M = 400005; //N 点数,M 主函数中调用 add 的次数
struct Dominator_Tree{
    private: struct eg{ int v, next; }e[M*2+N];
    int tot, cnt, h[N], iw[N], li[N], fa[N], fo[N], vo[N], sdom[N], pre[N], bkt[N];
    inline void link(int h[],int u,int v){ e[++cnt] = {v,h[u]}, h[u] = cnt; }
    inline int eval(int p){ findf(p); return vo[p]; }
    int findf(int p){
        if(fo[p] == p) return p;
        int r = findf(fo[p]);
        if(sdom[vo[fo[p]]] < sdom[vo[p]]) vo[p] = vo[fo[p]];
        return fo[p] = r;
    }
    void dfs(int p){
        sdom[p] = iw[p] = ++tot, li[tot] = p;
        for(int i = h[p]; i; i = e[i].next)
        if(!iw[e[i].v]) dfs(e[i].v), fa[e[i].v] = p;
    }
    public: int idom[N];
    inline void add(int u, int v) { link(h, u, v), link(pre, v, u);}
    inline void work(int root){
        dfs(root); int p, fp;
        for(int j = tot; j >= 2; j--, bkt[fp] = 0){
            for(int i = pre[p=li[j]]; i; i=e[i].next)
                if(iw[e[i].v]) sdom[p] = min(sdom[p], sdom[eval(e[i].v)]);
            link(bkt, li[sdom[p]], p);
            for(int i = bkt[fp=fo[p]=fa[p]]; i; i=e[i].next){
                int u = eval(e[i].v);
                idom[e[i].v] = sdom[u] == sdom[e[i].v] ? fp : u;
            }
        }
        for(int i = 2; i <= tot; i++){
            p = li[i], sdom[p] = li[sdom[p]];
            if(idom[p] != sdom[p]) idom[p] = idom[idom[p]];
        }
    }
    inline void init(int n){
        tot = cnt = 0;
        for (int i = 0; i <= n; i++){
        	pre[i] = bkt[i] = h[i] = iw[i] = 0, fo[i] = vo[i] = i;
        	sdom[i] = idom[i] = 0;
        }
        
    }
}DT;

int ind[N];
vector <int> g[200010];
int father[200010][40]={0};
int depth[200010]={0};
int n,m;
bool visit[200010]={false};
//int root;

void dfs(int u)
{
    int i;
    visit[u]=true;
    for (i=0;i<g[u].size();i++)
        {
            int v=g[u][i];
            if ( !visit[v] )
             {
                    depth[v]=depth[u]+1;
                    dfs(v);
             }
        }   
}//深搜出各点的深度,存在depth中 

void bz()
{
    int i,j;
    for (j=1;j<=30;j++)
        for (i=1;i<=n;i++)
            father[i][j]=father[father[i][j-1]][j-1];
}//倍增,处理father数组,详情参照上述讲解 

int LCA(int u,int v)
{
    if ( depth[u]<depth[v] ) 
    {
        int temp=u;
        u=v;
        v=temp;
    }//保证深度大的点为u,方便操作 
    int dc=depth[u]-depth[v];
    int i;
    for (i=0;i<30;i++)//值得注意的是,这里需要从零枚举 
    {
        if ( (1<<i) & dc)//一个判断,模拟一下就会很清晰 
         u=father[u][i];
    }
    //上述操作先处理较深的结点,使两点深度一致 
    if (u==v) return u;//如果深度一样时,两个点相同,直接返回 
    for (i=29;i>=0;i--)
    {
        if (father[u][i]!=father[v][i])//跳2^j步不一样,就跳,否则不跳 
        {
            u=father[u][i];
            v=father[v][i];
        }
    }
    u=father[u][0];//上述过程做完,两点都在LCA下一层,所以走一步即可 
    return u;
}
void init()
{

	memset(father,0,sizeof(father));
	memset(depth,0,sizeof(depth));
	for(int i=0;i<=n;i++)
	{
		visit[i]=false;
		ind[i]=0;
		g[i].clear();
		
	}
}
int main()
{
    int t,T;
    read(t);
    T=t;
    while(t--)
    {
    	read(n);read(m);
    	DT.init(n);
    	init();
    	int u,v;
    	for(int i=1;i<=m;i++)
    	{
    		read(u);read(v);
    		DT.add(v,u);
    		ind[u]++;
    	}
    	
    	for(int i=1;i<=n;i++)
    	{
    		if(!ind[i]){
    			DT.add(0,i);
    		}
    	}
    	DT.work(0);
    	for(int i=1;i<=n;i++)
    	{
    		g[DT.idom[i]].push_back(i);
    		father[i][0]=DT.idom[i];
    	}
    	depth[0]=0;
    	dfs(0);
    	bz();
    	int q;
    	scanf("%d",&q);
    	for(int i=1;i<=q;i++)
    	{
    		scanf("%d%d",&u,&v);
    		printf("%d\n",depth[u]+depth[v]-depth[LCA(u,v)]);
    	}
    }
    return 0;
} 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值