滅絕樹學習小記
Tags:圖論
一、概述
聽這名字特別酷對吧
不像一個Noip滾粗選手能學的東西
所以只能當一個搬運工了
orzlitble
:https://blog.csdn.net/litble/article/details/83019578
滅絕樹和支配樹應該是一種東西
用於\(O((n+m)logn)\)或者\(O((n+m)\alpha)\)求解一類如下問題:
在一張捕食圖上(從捕食者向被捕食者有連邊),若某生物的所有食物都滅絕了,則該生物滅絕。
滅絕樹便是此圖的一種生成樹,使得滿足滅絕樹上某點滅絕,該點子樹內所有點都滅絕
二、實現方式
把圖分成以下幾種情況考慮
樹
本身就是自己的滅絕樹
DAG
- [ZJOI2012]災難
- [Codeforces757F]Team Rocket Rises Again
分以下幾步
- 求出DAG的拓撲序
- 照拓撲序,從出度為0的點開始,某點的滅絕樹上父親就是該點在DAG上的所有出點 在滅絕樹上的LCA
- 如果沒有出度,連向一個虛擬點0方便計算
找LCA用倍增實現即可
一般有向圖
例題:HDU4694 Important Sisters:求一般圖每個點支配樹上的祖先編號之和
\(orz\ Tarjan\)
首先把原圖\(dfs\)一遍,求出\(dfn\)序
半支配點
官方定義:\(semi[x]=min\{v |\)有路徑\(v->v0->v1...->v_k->x\)使得\(dfn[v_i]>dfn[x]\)對\(1<=i<=k\)成立\(\}\),\(min\)指\(dfn\)最小
通俗一點:從\(semi[x]\)到\(x\)的路徑,掐頭去尾,都走的\(dfn\)大於\(x\)的點
畫個圖大概就是如下,\(dfn[]=\{0,1,2,3,4,5,7,6\}\),\(2->6->5\)掐頭去尾的\(dfn\)都大於\(dfn[5]\)
重要性質:
以下祖先關系均指\(dfs\)樹的祖先關系
- 1.不在\(dfs\)樹上的邊一定是由\(dfn\)大的點指向\(dfn\)小的點
- 2.\(semi[x]\)一定是\(x\)的祖先
- 3.在\(dfs\)樹上從\(semi[x]\)向\(x\)連邊,刪掉其他邊,滅絕關系不變
性感地理解就是:\(semi[x]\)到\(x\)的路徑相當於是在\(dfs\)樹外有一條路,且\(semi[x]\)是離根最近的那個點,從\(semi[x]\)都走不到\(x\)了,那其他的點更走不到了
由此可以得出一種做法,求出\(semi\)后轉DAG的做法,復雜度\(O(nlogn)\)
求半支配點
十分的巧妙
按照\(dfn\)序從大到小做,對於\(x\),枚舉\(R\)存在\(R->x\)這條邊
for(int w=n;w>=2;w--)
{
int x=id[w],res=n;
for(int i=rA.head[x];i;i=rA.a[i].next)
{
int R=rA.a[i].to;//反圖
if(!dfn[R]) continue;//有可能root不能走到y
if(dfn[R]<dfn[x]) res=min(res,dfn[R]);
else find(R),res=min(res,dfn[semi[mn[R]]]);
}
//anc[x]表示x在dfs樹上的父親
semi[x]=id[res];fa[x]=anc[x];B.link(semi[x],x);
}
其中\(find(R)\)表示路徑壓縮的帶權並查集,維護\(R\)到其已經被搜過的祖先的 \(dfn\)的最小值\(mn[R]\),用\(semi[mn[R]]\)去更新\(semi[x]\)
然后例題就得到了一種\(O(nlog)\)的做法
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<queue>
#include<vector>
#include<cstring>
#define pb push_back
using namespace std;
const int N=1e5+10;
int n,m,ans[N],dfn[N],id[N],tot,dep[N];
int anc[N],fa[N],semi[N],mn[N],in[N],ff[20][N];
int q[N],top;
queue<int> Q;
vector<int> E[N];
struct Map
{
struct edge {int next,to;}a[N<<1];
int head[N],cnt;
void reset()
{
cnt=0;memset(head,0,sizeof(head));
}
void link(int x,int y) {a[++cnt]=(edge){head[x],y};head[x]=cnt;}
}A,rA,B,C;
void reset()
{
A.reset();rA.reset();B.reset();C.reset();
tot=top=0;
for(int i=1;i<=n;i++)
{
dfn[i]=id[i]=ans[i]=in[i]=anc[i]=dep[i]=0;
fa[i]=mn[i]=semi[i]=i;
E[i].clear();
for(int j=0;j<=18;j++) ff[j][i]=0;
}
}
void dfs(int x,int fr)
{
dfn[x]=++tot;id[tot]=x;
anc[x]=fr;B.link(fr,x);
for(int i=A.head[x];i;i=A.a[i].next)
if(!dfn[A.a[i].to]) dfs(A.a[i].to,x);
}
void dfscalc(int x,int fr)
{
ans[x]=x+ans[fr];
for(int i=C.head[x];i;i=C.a[i].next)
dfscalc(C.a[i].to,x);
}
int find(int x)
{
if(x==fa[x]) return x;
int tt=fa[x];fa[x]=find(fa[x]);
if(dfn[semi[mn[tt]]]<dfn[semi[mn[x]]]) mn[x]=mn[tt];
return fa[x];
}
int LCA(int x,int y)
{
if(dep[x]<dep[y]) swap(x,y);
int d=dep[x]-dep[y];
for(int i=18;i>=0;i--) if(d&(1<<i)) x=ff[i][x];
for(int i=18;i>=0;i--)
if(ff[i][x]^ff[i][y])
x=ff[i][x],y=ff[i][y];
return x==y?x:ff[0][x];
}
void Work()
{
dfs(n,0);
for(int w=n;w>=2;w--)
{
int x=id[w],res=n;
if(!x) continue;
for(int i=rA.head[x];i;i=rA.a[i].next)
{
int R=rA.a[i].to;
if(!dfn[R]) continue;
if(dfn[R]<dfn[x]) res=min(res,dfn[R]);
else find(R),res=min(res,dfn[semi[mn[R]]]);
}
semi[x]=id[res];fa[x]=anc[x];B.link(semi[x],x);
}
for(int x=1;x<=n;x++)
for(int i=B.head[x];i;i=B.a[i].next)
in[B.a[i].to]++,E[B.a[i].to].pb(x);
for(int x=1;x<=n;x++) if(!in[x]) Q.push(x);
while(!Q.empty())
{
int x=Q.front();Q.pop();q[++top]=x;
for(int i=B.head[x];i;i=B.a[i].next)
if(!--in[B.a[i].to]) Q.push(B.a[i].to);
}
for(int i=1;i<=top;i++)
{
int x=q[i],f=0,l=E[x].size();
if(l) f=E[x][0];
for(int j=1;j<l;j++) f=LCA(f,E[x][j]);
ff[0][x]=f;dep[x]=dep[f]+1;C.link(f,x);
for(int p=1;p<=18;p++) ff[p][x]=ff[p-1][ff[p-1][x]];
}
ans[0]=0;dfscalc(n,0);
}
int main()
{
while(scanf("%d%d",&n,&m)!=EOF)
{
reset();
for(int i=1,x,y;i<=m;i++)
scanf("%d%d",&x,&y),A.link(x,y),rA.link(y,x);
Work();
for(int i=1;i<n;i++) printf("%d ",ans[i]);
printf("%d\n",ans[n]);
}
return 0;
}
更高效的做法
性質
\(idom[x]\)表示\(x\)的支配點(滅絕樹/支配樹上的父親)
- 若x是y的祖先,則要么\(x\)是\(idom[y]\)的祖先,要么\(idom[y]\)是\(idom[x]\)的祖先
可以這樣性感地理解:如果\(x\)是祖先,那么\(x\)的食物來源(指向\(x\)的邊)就少,相對來說滅絕掉\(x\)更容易,那么\(idom[x]\)會相應地靠近\(x\),同理\(y\)什么都吃所以誇張地說就是要把草給滅絕了\(y\)才會滅絕
然后看不懂了。。真的不可理解了。。而且發現上邊一般圖的做法我並不一定理解到位 丟個鏈接甩鍋吧 https://www.cnblogs.com/fenghaoran/p/dominator_tree.html 應該很少會有這種毒瘤玩意,記得$O(nlogn)$的做法就好了,畢竟路徑壓縮並查集是可以被卡成$O(nlogn)$的:http://www.cnblogs.com/meowww/p/6475952.html