支配树

摘要:
因此,优势关系可以形成一棵树。对于一个点,它控制子树中的所有点,而它的祖先控制它。DAG的优势树的拓扑排序,则一个点的优势点的拓扑排序必须小于它自己的。由于拓扑序,拓扑序(˂x)的所有点的优势树都已经建立,并且可以相乘。示例P2597[ZJOI2012]对于灾难,创建一个新的源点,并将其连接到所有入口度为(0)的点,然后运行支配树。一般有向图的控制树的Lengauer-Tarjan算法定义了首先找到原始图的dfs树并找到dfs阶。只需要选择所有候选半分配点中最小的一个作为优势点。
定义

对于一个有起点 \(s\) 的有向图,如果把点 \(a\) 和所有与 \(a\) 相连的边删掉,\(s\) 无法到达 \(b\),那么称 \(a\)支配\(b\),称 \(a\)\(b\)支配点

支配关系具有传递性:即若 \(a\) 支配 \(b\)\(b\) 支配 \(c\),那么 \(a\) 支配 \(c\)

如果存在 \(a\) 支配 \(x\)\(b\) 支配 \(x\)\(a\neq b\),一定存在 \(a\) 支配 \(b\)\(b\) 支配 \(a\)

所以说,支配关系可以构成一棵,对于一个点,它支配它子树内的所有点,并且它的祖先都支配它。

DAG 的支配树

拓扑排序,那么一个点的支配点的拓扑序一定小于自己。按照拓扑序从小到大构建支配树,对于一个点 \(u\),原图上能够到达它的点为 \(v_{i},v_2,\cdots ,v_k\),那么需要有一个点同时支配 \(v_{i},v_2,\cdots ,v_k\),即支配树上 \(v_{i},v_2,\cdots ,v_k\)\(LCA\)。因为拓扑序的关系,拓扑序 \(<x\) 的所有点的支配树已经建好了,\(LCA\) 倍增即可。把 \(x\) 接到 \(LCA\) 的儿子上即可。

例题

P2597 [ZJOI2012]灾难

新建一个源点连向所有入度为 \(0\) 的点,然后跑支配树即可。

CF757F Team Rocket Rises Again

首先跑一遍 Dijkstra,对于一个点 \(x\),对于所有 \(dis_x=dis_u+w_{u\to v}\) 的边,在新图上连边 \(u\to x\),显然这个新图是一个 DAG,跑支配树即可。

普通有向图的支配树

Lengauer-Tarjan 算法

定义

首先找到原图的一棵 dfs 树,求出 dfs 序 \(dfn_u\)

半支配点:一个点 \(u\) 的半支配点定义为:所有存在一条只经过 \(dfn_x>dfn_u\) (不含端点)的点 \(x\) 的路径,从 \(k\) 走到 \(x\)的点 \(k\) 中,\(dfn\) 最小的点。记为 \(semi_u\)。以下简称【存在一条只经过 \(dfn_x>dfn_u\) (不含端点)的点 \(x\),从 \(k\) 走到 \(x\)】的点 \(k\)候选半支配点。只需要在所有候选半支配点里选 \(dfn\) 最小的一个即为支配点。

两个基本性质:

  • \(dfn_{semi_u}<dfn_u\),因为显然 \(u\) 的父亲一定合法。
  • \(semi_u\) 一定是 \(u\)\(dfs\) 树上的祖先。

如何求 半支配点

考虑一条边 \(y\to x\)

  • \(dfn_y<dfn_x\)\(y\)\(x\) 的候选半支配点。
  • \(dfn_y>dfn_x\):那么 \(y\) 的所有 \(dfn_u>dfn_x\) 的祖先 \(u\) 的候选半支配点均是 \(x\) 的候选半支配点,我们只取里面最有用的,也就是 \(y\) 的所有 \(dfn_u>dfn_x\)祖先 \(u\) 的半支配点均是 \(x\)候选半支配点

这两个条件是充要的。

考虑按照 \(dfn\) 序从大往小枚举点 \(u\),枚举所有原图上能到达它的点 \(v\)(即反图上的出边),若 \(dfn_v<dfn_u\),用 \(v\) 更新 \(semi_u\);否则,设 \(l=LCA(u,v)\),我们需要求出 \(l\)\(v\) 的链上(不含 \(l\))的点中 \(dfn\) 最小的 \(semi_k\)

由于是倒序枚举 \(dfn\),此时 \(l\) 还未被加进来,而 \(l\)\(v\) 的链上已经全都加进来了。采用带权并查集,每个点维护自己到根(已连通的部分的根)的所有点中,\(dfn_{semi_x}\) 最小的 \(x\) 是哪个。现在,询问 \(l\)\(v\) 的链上(不含 \(l\))的点中 \(dfn\) 最小的 \(semi_k\) 转化为询问并查集上到根的链的最小值,使用路径压缩做到 \(O(n\log n)\),注意没有也不能按秩合并,所以复杂度不是 \(O(n\alpha(n))\),不过常数很小,近似 \(O(n\alpha(n))\)。找到这个点 \(k\),用 \(semi_k\) 更新 \(semi_u\) 即可。

inline int Min(int x,int y){return dfn[semi[x]]<dfn[semi[y]]?x:y;}
namespace dsu{
	int fa[N],val[N];
	int find_fa(int x){
		if(x==fa[x])return x;
		int f=fa[x];
		fa[x]=find_fa(fa[x]);
		val[x]=Min(val[x],val[f]);
		return fa[x];
	}
	inline int query(int x){
		find_fa(x);
		return val[x];
	}
}

求出 \(u\)\(semi\) 之后,把 \(u\) 的儿子与 \(u\) 合并。

如何求支配点

显然需要用到刚才讲过的 半支配点。

对于 \(u\) 和它的半支配点 \(semi_u\),需要求出 \(semi_u\)\(u\) 的链上(不含 \(semi_u\))的所有点中,\(dfn_{semi_x}\) 最小的 \(x\)

  • \(semi_x=semi_u\),那么 \(u\) 的支配点就是 \(semi_u\)
  • 否则(显然只有可能 \(dfn_{semi_x}<dfn_{semi_u}\)),\(u\) 的支配点就是 \(x\) 的支配点。

现在我们要求的就是:\(semi_u​\)\(u​\) 的链上(不含 \(semi_u​\))的所有点中,\(dfn_{semi_x}​\) 最小的 \(x​\)。这一部分和上面求半支配点中【求出 \(l​\)\(v​\) 的链上(不含 \(l​\))的点中 \(dfn​\) 最小的 \(semi_k​\) 】很相似。

可以在求 \(semi\) 的时候同时求出支配点,也就是把求 \(x\) 的支配点时,要询问 \(semi_u\)\(u\) 的链这部分,离线到求解 \(semi_u\) 的半支配点时。在这时,\(semi_u\)\(u\) 的链(不含 \(semi_u\))已经合并完了,询问 \(u\) 即可得到链上 \(dfn_{semi_x}\) 最小的 \(x\),然后进行上面的讨论即可。

至此我们求出了支配点,也就求出了支配树,复杂度 \(O(n\log n)\)

以下是 P5180 【模板】支配树的代码:

#include <bits/stdc++.h>
using namespace std;
inline int read(){
	int s=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9')s=(s<<3)+(s<<1)+ch-'0',ch=getchar();
	return s*f;
}
const int N=2e5+5,M=3e5+5;
int n,m;
int dfn[N],semi[N],idom[N],node[N],inde;
inline int Min1(int x,int y){return dfn[x]<dfn[y]?x:y;}
inline int Min(int x,int y){return dfn[semi[x]]<dfn[semi[y]]?x:y;}
namespace dsu{
	int fa[N],val[N];
	int find_fa(int x){
		if(x==fa[x])return x;
		int f=fa[x];
		fa[x]=find_fa(fa[x]);
		val[x]=Min(val[x],val[f]);
		return fa[x];
	}
	inline int query(int x){
		find_fa(x);
		return val[x];
	}
}
struct Edge{int to,next;};
struct Graph{
	Edge e[M];
	int head[N],ecnt;
	inline void adde(int u,int v){
		e[++ecnt]=(Edge){v,head[u]};head[u]=ecnt;
	}
	inline Edge& operator [](int x){return e[x];}
}e,e1,tr,e2;
#define pb push_back
void dfs1(int u){
	dfn[u]=++inde;node[inde]=u;
	for(int i=e.head[u],v;i;i=e[i].next)
		if(!dfn[v=e[i].to])dfs1(v),e2.adde(u,v);
}
int sz[N];
void dfs2(int u){
	sz[u]=1;
	for(int i=tr.head[u],v;i;i=tr[i].next)
		dfs2(v=tr[i].to),sz[u]+=sz[v];
}
int mn[N];
vector<int> buc[N];
int main(){
	n=read();m=read();
	for(int i=1;i<=n;++i)mn[i]=i,semi[i]=i,dsu::val[i]=i,dsu::fa[i]=i;
	for(int i=1,u,v;i<=m;++i){
		u=read();v=read();
		e.adde(u,v);
		e1.adde(v,u);
	}
	dfs1(1);
	for(int i=n,u;i>=1;--i){
		u=node[i];
		for(int x:buc[u])mn[x]=dsu::query(x);
		for(int i=e1.head[u],v;i;i=e1[i].next){
			v=e1[i].to;
			if(dfn[v]<dfn[u])semi[u]=Min1(semi[u],v);
			else semi[u]=Min1(semi[u],semi[dsu::query(v)]);
		}
		for(int i=e2.head[u];i;i=e2[i].next)
			dsu::fa[e2[i].to]=u;
		buc[semi[u]].pb(u);
	}
	idom[1]=1;
	for(int i=2,u;i<=n;++i){
		u=node[i];
		if(semi[mn[u]]==semi[u])idom[u]=semi[u];
		else idom[u]=idom[mn[u]];
		tr.adde(idom[u],u);
	}
	dfs2(1);
	for(int i=1;i<=n;++i)printf("%d ",sz[i]);
	return 0;
}

免责声明:文章转载自《支配树》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇配置ftp服务器只能上传不能进行其他操作内中断(学习汇编)下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

随便看看

SpringBoot项目中@Async方法没有执行的问题分析

现象:1.明显的现象:在日志文件中找不到方法中的日志输出,并且没有错误报告(即,未执行@Async标记的方法,也没有错误报告)。2.分析现象:日志中某段时间后没有任务xxx线程的日志原因:@Async异步方法默认使用Spring创建ThreadPoolTaskExecutor(参考TaskExecutionAutoConfiguration),其中默认核心线...

JAVA 实现CLOB转String

CLOB定义了用于在数据库中保存文件的类型。SQLCLOB是一种内置类型,它将一个大型字符对象作为列值存储在数据库表的一行中。默认情况下,驱动程序使用SQLlocator实现Clob对象,这意味着Clob对象包含指向SQLCLOB数据的逻辑指针,而不是数据本身。Clob对象在其创建的事务期间有效。在一些数据库系统中,文本也用作CLOB的别名。例如,SQL S...

Qt中使用定时器(可使用QObject::timerEvent定时执行,QTimer::singleShot可只触发一次)

在Qt中使用定时器有两种方法,一种是使用QObiect类的定时器;一种是使用QTimer类。当定时器触发时,应用程序会发送一个QTimerEvent。与定时器相关的成员函数有:startTimer()、timeEvent()、killTimer()。virtualvoidQObject::timerEvent;虚函数timerEvent()被重载来实现用户的...

Qt 调用本地浏览器打开URL

单击一些Qt控件以查找本地浏览器传递的URL以打开前端。...

ubuntu中VNC的安装配置笔记

设置密码并首次启动vncserver后。vnc/directory将在用户的主目录中生成。注意:安装后,用户的主目录中没有vnc目录。这是因为默认情况下启用了桌面配置,并且需要修改配置文件。后来,我在网上找到了一篇可靠的文章:http://blog.csdn.net/njchenyi/article/details/8489689本文中描述的配置方法确实可行...

季调方法论

理论与实践“季节性调整原则季节性调整方法分析季节性调整实践中遇到的问题只有同比数据缺少春节效应阅读”通货膨胀的季节性调整和预测模型“通货膨胀预测CPI的季节性调整具有明显的春节效应考虑春节效应的季节性调节春节效应的确定CPI的季节调整基于季节性调整后CPI的预测通货膨胀的修正(应对非洲猪瘟的影响)修订并扩大了季度调查方法的CPI预测读数...