zoukankan      html  css  js  c++  java
  • 支配树

    一、前言

    黑科技杀我!

    想写什么写什么,所以这篇文章除了我,可能不适合其他人学习。

    其实就是无情的搬运工,并且懒得搬证明。

    而且有些地方个人认为有些问题并自行纠错,有可能我反而搞错了,谨慎阅读。

    二、讲解

    (零)、前提

    存在起点 (s) 的连通有向图。

    (一)、基本概念

    0.符号含义与特别说明

    树一般指(dfs)树。树边、返祖边、前向边、横叉边也很好理解。

    (u ightarrow v):表示存在一条 (u)(v)

    (uoverset{+}{ ightarrow} v):表示存在一条 (u)(v) 的树边且 (u ot=v)

    (uoverset{.}{ ightarrow} v):表示存在一条 (u)(v) 的树边但允许 (u=v)

    (uleadsto v):表示存在一条由 (u)(v)路径

    在本文中,(dfn)值 和(dfs)序等价。

    1.最近支配点idom

    (i) 的支配点中(dfs)序最大的点即为点 (i) 的最近支配点,换句话说,点 (i) 在支配树中的父亲即它的最近支配点。最近支配点一般用 (idom) 表示。

    2.半支配点sdom

    顶点 (v) 的半支配点 (u) 是所有符合下列条件的点中 (dfs)序 最小的点:

    • 顶点 (i) 存在一条路径到 (v) 且路径上的顶点(不包含两个端点)的(dfs)序均大于(v)(dfs)序。

    (v) 的半支配点记为 (sdom(v))

    特别的,若 (u ightarrow v)(u) 也是 (sdom(v)) 的候选点。这种情况相当于路径上没有其他点。

    (二)、五大引理

    1.

    (s) 以外的每个点存在唯一的 (idom)

    证明:过于显然,略。

    2.

    (forall w ot=s),有 (idom(w)overset{+}{ ightarrow}w)

    (idom(w)) 是树中 (w) 的祖先。

    证明:反证,考虑如果去掉 (idom(w))(s) 可以通过树边到达 (w)

    3.

    (forall w ot=s),有 (sdom(w)overset{+}{ ightarrow}w)

    (sdom(w)) 是树中 (w) 的祖先。

    证明:咕。

    4.

    (forall w ot=s),有 (idom(w)overset{.}{ ightarrow}sdom(w))

    (idom(w)) 要么是 $sdom(w) 的祖先,要么是 (sdom(m)) 本身。

    证明:根据引理二和引理三再反证即可。

    5.

    对于满足 (uoverset{.}{ ightarrow}v) 的点 (u,v),则有 (uoverset{.}{ ightarrow}idom(v))(idom(v)overset{.}{ ightarrow}idom(u))

    读者自证不难。

    (三)、三项定理

    1.

    (forall u ot=s),如果对于所有满足 (sdom(u)overset{+}{ ightarrow}voverset{.}{ ightarrow}u)(v),有 (dfn(sdom(v))ge dfn(sdom(u))),则有 (idom(u)=sdom(u))

    读者自证不难。

    2.

    (forall u ot=s),如果有 (sdom(u)overset{+}{ ightarrow}voverset{.}{ ightarrow}u),设 (v) 是满足 (dfn(sdom(v))) 最小的一个 (v),若 (dfn(sdom(v)) < dfn(sdom(u))),则有 (idom(u)=idom(v))

    读者自证不难。

    3.

    读者自证不难。

    (四)、重要推论

    (forall u ot=s),令 (u) 为所有满足 (sdom(v)overset{+}{ ightarrow}uoverset{.}{ ightarrow}v)(u)(dfn(sdom(u))) 最小的一个点,有:

    [idom(v) egin{cases} sdom(v) (sdom(u)=sdom(v))\ idom(u) (dfn(sdom(u))<dfn(sdom(v))) end{cases}]

    (五)、具体实现

    1.(O(nlog_2n))

    • 如果是一棵树,那么它本身就是支配树。

    • 如果是一个 (DAG),那么可以拓扑排序,依次确定每个点的 (idom)。具体操作为在原图中找出其所有前驱在支配树上的 (LCA),这个就是它的 (idom)

    • 如果是一个普通有向图,那么我们可以先求出每个点的 (sdom),然后删掉所有非树边并连边 ((sdom(u),u)),此时得到一个 (DAG),支配关系与原图一致。

    2.(O(alpha(n)n)))

    利用推论即可。

    三、练习

    板题(洛谷)

    四、代码

    板题 ((O(nlog_2n)))

    //12252024832524
    #include <queue>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #define TT template<typename T>
    using namespace std; 
    
    typedef long long LL;
    const int MAXN = 200005;
    const int MAXM = 300005 << 1;
    int n,m;
    
    LL Read()
    {
    	LL x = 0,f = 1;char c = getchar();
    	while(c > '9' || c < '0'){if(c == '-') f = -1;c = getchar();}
    	while(c >= '0' && c <= '9'){x = (x*10) + (c^48);c = getchar();}
    	return x * f;
    }
    TT void Put1(T x)
    {
    	if(x > 9) Put1(x/10);
    	putchar(x%10^48);
    }
    TT void Put(T x,char c = -1)
    {
    	if(x < 0) putchar('-'),x = -x;
    	Put1(x); if(c >= 0) putchar(c);
    }
    TT T Max(T x,T y){return x > y ? x : y;}
    TT T Min(T x,T y){return x < y ? x : y;}
    TT T Abs(T x){return x < 0 ? -x : x;}
    
    struct EDGE
    {
    	int head[MAXN],tot;
    	struct edge
    	{
    		int v,nxt;
    	}e[MAXM];
    	void Add_Edge(int x,int y)
    	{
    		e[++tot].v = y;
    		e[tot].nxt = head[x];
    		head[x] = tot;
    	}
    }e,re,eg,reg,dt;//原图(边),反图(边),等价图,反等价图,支配树
    
    int dfn[MAXN],rdfn[MAXN],dfntot,fa[MAXN];
    void dfs(int x)
    {
    	rdfn[dfn[x] = ++dfntot] = x;
    	for(int i = e.head[x]; i ;i = e.e[i].nxt)
    	{
    		int v = e.e[i].v;
    		if(dfn[v]) continue;
    		fa[v] = x;
    		dfs(v); 
    		eg.Add_Edge(x,v); 
    	}
    }
    int F[MAXN],sdom[MAXN],MIN[MAXN];
    int findSet(int x)
    {
    	if(x == F[x]) return F[x];
    	int ftr = F[x]; F[x] = findSet(F[x]);
    	if(dfn[sdom[MIN[x]]] > dfn[sdom[MIN[ftr]]]) MIN[x] = MIN[ftr];
    	return F[x];
    }
    void Tarjan()
    {
    	for(int i = 1;i <= n;++ i) sdom[i] = MIN[i] = F[i] = i;
    	for(int i = n;i > 1;-- i)//枚举的是dfs序
    	{
    		int x = rdfn[i];//当前更新节点
    		if(!x) continue;//不连通
    		int ret = i;
    		for(int j = re.head[x]; j ;j = re.e[j].nxt) 
    		{
    			int v = re.e[j].v;
    			if(!dfn[v]) continue;
    			if(dfn[v] < dfn[x]) ret = Min(ret,dfn[v]);
    			else
    			{
    				findSet(v);
    				ret = Min(ret,dfn[sdom[MIN[v]]]); 
    			}
    		}
    		sdom[x] = rdfn[ret];
    		F[x] = fa[x];
    		eg.Add_Edge(sdom[x],x); 
    	}
    } 
    int d[MAXN],f[MAXN][18];
    int lca(int x,int y) 
    {
    	if(x == y) return x;
    	if(d[x] < d[y]) swap(x,y);
    	for(int i = 17;i >= 0;-- i)
    		if(d[f[x][i]] >= d[y]) x = f[x][i];
    	if(x == y) return x;
    	for(int i = 17;i >= 0;-- i)
    		if(f[x][i] != f[y][i])
    			x = f[x][i],y = f[y][i];
    	return f[x][0];
    }
    void bdt(int x)//build dominating tree
    {
    	int LCA = 0;
    	for(int i = reg.head[x]; i ;i = reg.e[i].nxt) 
    	{
    		int v = reg.e[i].v;
    		if(!LCA) LCA = v;
    		else LCA = lca(LCA,v);
    	}
    	f[x][0] = LCA;
    	d[x] = d[LCA] + 1;
    	dt.Add_Edge(LCA,x);
    	for(int i = 1;i <= 17;++ i) f[x][i] = f[f[x][i-1]][i-1];
    }
    int in[MAXN];
    void topu()
    {
    	for(int x = 1;x <= n;++ x)
    		for(int i = eg.head[x]; i ;i = eg.e[i].nxt)
    		{
    			int v = eg.e[i].v;
    			in[v]++;
    			reg.Add_Edge(v,x);
    		}
    	queue<int> q;
    	for(int i = 1;i <= n;++ i) if(!in[i]) q.push(i),bdt(i);
    	while(!q.empty())
    	{
    		int x = q.front(); q.pop();
    		for(int i = eg.head[x]; i ;i = eg.e[i].nxt)
    		{
    			int v = eg.e[i].v;
    			if(!(--in[v])) q.push(v),bdt(v);
    		}
    	}
    }
    int siz[MAXN];
    void dtdfs(int x)
    {
    	siz[x] = 1;
    	for(int i = dt.head[x],v; i ;i = dt.e[i].nxt)
    		v = dt.e[i].v,dtdfs(v),siz[x] += siz[v];
    }
    
    int main()
    {
    //	freopen(".in","r",stdin);
    //	freopen(".out","w",stdout);
    	n = Read(); m = Read();
    	for(int i = 1,u,v;i <= m;++ i) u = Read(),v = Read(),e.Add_Edge(u,v),re.Add_Edge(v,u);
    	dfs(1);
    	Tarjan();
    	topu();
    	dtdfs(0);
    	for(int i = 1;i <= n;++ i) Put(siz[i],' ');
    	return 0;
    }
    

    五、吐槽

    不会真有人写 (O(nlog_2n)) 的算法吧。

  • 相关阅读:
    搜索自动提示的简单模拟JQuery
    log4j+AOP 记录错误日志信息到文件中
    利用firebug 查看JS方法, JS 调试
    Blog 使用Jsoup解析出html中的img元素
    jquery操作select(取值,设置选中)
    C++解析(20):智能指针与类型转换函数
    C++解析(19):函数对象、关于赋值和string的疑问
    C++解析(18):C++标准库与字符串类
    C++解析(17):操作符重载
    C++解析(16):友元与类中的函数重载
  • 原文地址:https://www.cnblogs.com/PPLPPL/p/14671023.html
Copyright © 2011-2022 走看看