支配树
对于任意两个点 \(u, v\),若从源点出发到达顶点 \(v\) 的所有路径都需要经过顶点 \(u\),则称顶点 \(u\) 支配顶点 \(v\)。特别地,每个顶点支配其自身。
对于任意一个点 \(v\),我们将图中支配顶点 \(v\) 的顶点集合称为 \(v\) 的受支配集 \(D_v\)。
支配树是这样一棵树,它的点集为原图点集,对于每个点 \(v\),它在支配树上对应的节点到根的链上所有点等于 \(D_v\)。
不难发现这样的结构肯定是存在的。
支配集的求法:删掉这个点,看看跟源点还连通的有哪些。复杂度是 \(o(n^2)\) 的。
支配树的求法:
对于一般图而言,常有 \(O(n^2)\) 的求支配树的方法。即对于每个点的支配集中的点按照到源点(支配树根)的 dis 从小到大排序,对于相邻两点,若树边不存在则连接。
对于 DAG 而言,存在 \(O(n\log n)\) 的求支配树方法。无需求支配集,而只需要对所有点按照拓扑序枚举,试图实时维护已枚举到的所有点的支配树;对于当前点,枚举所有指向它的点,它们在当前支配树上的共同的 lca 就是当前点在支配树上的父亲。
【例1】[联合省选2021A]支配
本题在建立支配树之后分类讨论,假如加入的是返祖边,那么不影响,否则——
设这条边是 \(x\to y\),\(l=\text{lca}(x,y)\)。
受影响的点必然在 \(l\) 的子树中。
对于每个 \(y\) 子树中的点,考虑它在原图中的出边指向的点 \(p\),若此点在 \(x\) 的子树中,并且它不是 \(x\) 的儿子,那么它一定是满足条件的;
从满足条件的点出发可以影响到一定范围内的点,它们因满足条件的点的支配集改动而改动:从一个满足条件的点出发枚举它在原图中的出边指向的点,若此点在 \(l\) 的子树中,并且它到 \(\text{lca}(p,x)\) 的距离要 \(\ge 2\)。
可以通过画几张图得到这个做法。
(
)
#include <bits/stdc++.h>
using namespace std;
inline int read(){
register char ch=getchar();register int x=0;
while(ch<'0'||ch>'9')ch=getchar();
while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
return x;
}
void print(int x){
if(x/10)print(x/10);
putchar(x%10+48);
}
const int N=3005;
int n,m,q,tmp,dfc,dis[N][N],ord[N],fa[N],siz[N],dep[N],dfn[N],out[N],lca[N][N];
bitset<N>in_tree,vis,zp[N],good,bk;
vector<int>G[N],vec,H[N],T[N];
queue<int>Q;
struct E{int u,v;}e[N*2];
void dfs(int x,int p){
dep[x]=dep[p]+1,fa[x]=p,siz[x]=1,dfn[x]=++dfc;
for(int y:T[x])if(y^p)dfs(y,x),siz[x]+=siz[y];
out[x]=dfc;
}
int X,Y;
void dfs1(int x,int p){
if(dfn[x]>=dfn[Y]&&dfn[x]<=out[Y]){
good[x]=1;
for(int y:G[x])if(dfn[y]>=dfn[X]&&dfn[y]<=out[X]&&dep[y]-1>dep[lca[y][X]])good[y]=1;
}
for(int y:T[x])if(y^p){
dfs1(y,x);
}
}
void dfs2(int x,int p){
good[x]=1;
for(int y:G[x])if(!good[y]&&dep[y]-1>dep[lca[y][X]]){
dfs2(y,x);
}
}
vector<int>rou;
void Dfs(int x,int p){
rou.emplace_back(x);
for(int y:rou)lca[x][y]=lca[y][x]=y;
for(int y:T[x])if(y^p)Dfs(y,x);
rou.pop_back();
}
int Lca(int x,int y){
if(lca[x][y])return lca[x][y];
return lca[x][y]=Lca(fa[x],y);
}
int main(){
n=read(),m=read(),q=read();
for(int i=1;i<=m;i++){
e[i].u=read(),e[i].v=read();
G[e[i].u].push_back(e[i].v);
}
memset(dis,0x3f,sizeof dis);
for(int st=1;st<=n;st++){
Q.push(st),dis[st][st]=0;
while(!Q.empty()){
int x=Q.front();Q.pop();
if(st==1)ord[++tmp]=x;
for(int y:G[x])
if(dis[st][y]>dis[st][x]+1)
dis[st][y]=dis[st][x]+1,Q.push(y);
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++)vis[j]=0;
if(i!=1)Q.push(1),vis[1]=1;
while(!Q.empty()){
int x=Q.front();Q.pop();
for(int y:G[x])if(y!=i&&!vis[y]){
vis[y]=1;
Q.push(y);
}
}
for(int j=1;j<=n;j++)if(!vis[j])zp[i][j]=1;
}
in_tree[1]=1;
for(int i=1;i<=n;i++){
vec.clear();
for(int j=1;j<=n;j++)if(zp[ord[j]][i]){
vec.emplace_back(ord[j]);
}
for(int j=1;j<vec.size();j++)if(!in_tree[vec[j]])
in_tree[vec[j]]=1,T[vec[j-1]].emplace_back(vec[j]);
}
dfs(1,0);
Dfs(1,0);
for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)Lca(i,j);
for(int x,y;q--;){
x=read(),y=read();
int l=lca[x][y];
if(y==l||dep[y]==dep[l]+1){puts("0");continue;}
X=x,Y=y;
dfs1(1,0);
for(int i=good._Find_first();i<=n;i=good._Find_next(i))dfs2(i,0);
int ans=0;
for(int i=good._Find_first();i<=n;i=good._Find_next(i))ans++,good[i]=0;
print(ans),putchar('\n');
}
}
【例2】[ZJOI2012]灾难
按照正文中的方法做就好了。注意建立超级源点。
#include <bits/stdc++.h>
using namespace std;
const int N=65550;
int n,tot,fa[N][17],ord[N],in[N],dep[N],siz[N];
vector<int>G[N],vec[N],T[N];
queue<int>Q;
int glca(int u,int v){
if(!u)return v;
if(u==v)return u;
if(dep[u]>dep[v])swap(u,v);
for(int i=16;~i;i--)if(dep[fa[v][i]]>=dep[u])v=fa[v][i];
if(u==v)return u;
for(int i=16;~i;i--)if(fa[u][i]!=fa[v][i])u=fa[u][i],v=fa[v][i];
return fa[u][0];
}
void dfs(int x,int p){
siz[x]=1;
for(int y:T[x])dfs(y,x),siz[x]+=siz[y];
}
int main(){
scanf("%d",&n);
for(int i=1,t;i<=n;i++){
while(scanf("%d",&t),t)vec[i].emplace_back(t),G[t].emplace_back(i),in[i]++;
}
dep[n+1]=1;
for(int i=1;i<=n;i++)if(!in[i])fa[i][0]=n+1,dep[i]=2,T[n+1].emplace_back(i),vec[i].emplace_back(n+1),Q.push(i);
//"vec[i].emplace_back(n+1)"很容易漏
while(!Q.empty()){
int x=Q.front();Q.pop();
ord[++tot]=x;
for(int y:G[x]){
if(!--in[y])Q.push(y);
}
}
for(int r=1;r<=n;r++){
int i=ord[r];
int l=0;
for(int j:vec[i])l=glca(l,j);
T[l].emplace_back(i),dep[i]=dep[l]+1,fa[i][0]=l;
for(int j=1;j<=16;j++)fa[i][j]=fa[fa[i][j-1]][j-1];
}
dfs(n+1,0);
for(int i=1;i<=n;i++)printf("%d\n",siz[i]-1);
}