支配树学习笔记
先挖个坑,以后慢慢添
支配点是什么
从起点开始到另一个点,绕不开的点就是支配点
为什么支配关系不变
命题:为什么删除非树边加上半支配点到该点的边支配关系不变
- 对于某定点x而言,要么是支配点,要么不是,无中间态,所以我们可以考虑反面
- 只有x的祖先才有资格做支配点
- 考虑某个备选y,如果它可以被略过(可以从y的某祖先w到y下x上的点z而不经过y),那么y淘汰
- 这个w至z的路径哪部分可以淘汰人?非树边的部分。
- 对这条路径掐头去尾,去掉树边
- 然后我们就可以发现,w至z的路径中都是dfn大于z的点了,w就可以做z的半支配点备选了
- 由于半支配点是取最上面的那个,所以即使w不是z的半支配点,w至z中的任意点都会被淘汰
- 所以该淘汰的都会被淘汰
- 那为什么未被淘汰的点的都是支配点的呢?
- 很简单,未被淘汰的点都没有办法从上面绕到下面而不经过他,所以都是支配点
代码
#include<bits/stdc++.h>
#define now v[i]
using namespace std;
const int sz=3e5+527;
int T;
int n,m;
int u,v;
int c[sz];
int fa[sz];//dfs树上的父亲
int f[sz],mi[sz];//f为并查集上的父亲,mi保存了并查集中semi的dfn最小的点
int rnk[sz],dfn[sz];//排名和时间戳
int semi[sz],idom[sz];//半支配点和支配点
struct chart{
int cnt;
int v[sz],nxt[sz];
int head[sz];
void add(int u,int vv){
cnt++;
v[cnt]=vv;
nxt[cnt]=head[u];
head[u]=cnt;
}
}g,fg,ng,tr;
//g为原图,fg为反图,ng为semi的图,tr为支配树
int find(int x){
if(f[x]==x) return x;
int tmp=f[x];
f[x]=find(f[x]);
if(dfn[semi[mi[tmp]]]<dfn[semi[mi[x]]]) mi[x]=mi[tmp];
return f[x];
}//并查集
void dfs(int x){
rnk[dfn[x]=++T]=x;
for(int i=g.head[x];i;i=g.nxt[i])
if(!dfn[g.now]) fa[g.now]=x,dfs(g.now);
}//建出dfs树
void work(){
for(int i=n;i>=2;i--){
//从dfn最大的点开始
u=rnk[i];
int tmp=n;
for(int j=fg.head[u];j;j=fg.nxt[j]){
v=fg.v[j];
if(dfn[v]<i) tmp=min(tmp,dfn[v]);//dfn比自己小的只能做起点
else if(dfn[v]>i){
find(v);
tmp=min(tmp,dfn[semi[mi[v]]]);
}
}
semi[u]=rnk[tmp];
f[u]=fa[u];
ng.add(semi[u],u);
u=rnk[i-1];//因为rnk[i]已经和父亲连边了+需要rnk[1]
for(int j=ng.head[u];j;j=ng.nxt[j]) {
v=ng.v[j];
find(v);
if(semi[mi[v]]==semi[v]) idom[v]=semi[v];//如果支配点等于被支配点
else idom[v]=mi[v];//否则记录u的支配点等于谁的支配点
}
}
for(int i=2;i<=n;++i) {
u=rnk[i];
if(idom[u]!=semi[u]) idom[u]=idom[idom[u]];
tr.add(idom[u],u);
}
for(int i=n;i>=1;i--){
u=rnk[i];
c[u]=1;
for(int i=tr.head[u];i;i=tr.nxt[i]){
v=tr.v[i];
c[u]+=c[v];
}
}
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
scanf("%d%d",&u,&v);
g.add(u,v);
fg.add(v,u);
}
for(int i=1;i<=n;i++) semi[i]=mi[i]=f[i]=i;
dfs(1);
work();
for(int i=1;i<=n;i++) printf("%d ",c[i]);
}