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

被折叠的 条评论
为什么被折叠?



