圓方樹,是解決仙人掌問題的實用方法,假設最初圖都是圓點,對於每個環新建一個方點並連線這個環上所有圓點,能很好規避同一個點可能屬於很多個環的情況,並且發現build完之後是一棵樹
廣義圓方樹,能夠不侷限於去解決仙人掌問題,能上升到無向圖層面,很好解決圖上路徑類,等等問題
那麼如何建立圓方樹?有點類似 \(v-dcc\) ,建立方點,連線當前點雙聯通分量的所有點,實現透過tarjan演算法
但注意 \(v-dcc\) 把整個點雙聯通分量都縮成一個點了,圓方樹還保持著圓點,也就是說圓方樹點數是 \(n+k\) ,其中 \(k\) 標號是點雙個數
具體實現不詳講,但存在值得注意的細節:
令 \(now\) 為當前 \(dfs\) 到的節點, \(y\) 為其搜尋樹上的一個兒子。注意, \(now\) 與 \(y\) 在棧中不一定相鄰。也就是說,下面兩種寫法:
- 彈出棧頂直到彈出 \(now\) 為止;最後再壓入 \(now\)
- 彈出棧頂直到彈出 \(y\) 為止,最後再將虛點向 \(now\) 連邊
前者錯誤,後者正確。
程式碼:
void tarjan(int x){
++nown;
dfn[x]=low[x]=++num;
st.push(x),w[x]=-1;
for(int i=head[x];i;i=edge[i].Next){
int to=edge[i].to;
if(!dfn[to]){
tarjan(to);
low[x]=min(low[x],low[to]);
if(low[to]>=dfn[x]){
addedge2(++diannum,x),addedge2(x,diannum);
++w[diannum];
while(1){
addedge2(diannum,st.top()),addedge2(st.top(),diannum);
++w[diannum];
if(st.top()==to){
st.pop();
break;
}
st.pop();
}
}
}
else low[x]=min(low[x],dfn[to]);
}
}
\(v-dcc\) 和圓方樹運用區別何在?後者對於點雙內部的處理能夠非常方便,而前者似乎處理整個點雙對答案的貢獻(不考慮單點)會十分好搞
圓方樹的性質:
-
是樹
-
每條邊都是方點和圓點連線邊
-
每個方點對應一個點雙聯通分量
-
方點的度數是點雙聯通分量的大小
-
圓點是割點才有超過1個兒子,否則只連線一個方點兒子
-
圓方樹上兩個點的路徑經過的圓點是圖上兩點之間的必經點
還有一些點雙的小性質:對於一個點雙的兩點,它們之間簡單路徑的並集等於這個點雙集合
圓方樹能夠很好地將無向圖上問題轉化為樹上問題,進行統計類的時候可能割點會被統計多次,所有一般把方點賦為-1,然後就很好做了,等等就不細說了