算法 {获取所有的生成树形态}
獲取所有的生成樹
定義
對於一個連通無向圖, 獲取其所有形態的生成樹;
性質
8
8
8個點的完全圖, 他有
262144
262144
262144個生成樹;
9
9
9個點的完全圖, 他有
4782969
4782969
4782969個生成樹;
使用該算法的場景 通常此時點數不會太多;
算法
枚舉(從 M M M條邊中 選擇 N − 1 N-1 N−1條邊)的所有方案
性質
一個連通的無向圖, N個點 M條無線邊, 且他簡單圖 即沒有自環/重邊;
算法基本思路是: 枚舉
C
M
N
−
1
C_M^{N-1}
CMN−1, 因為生成樹有
N
−
1
N-1
N−1條邊, 即從M條邊裡 選擇
N
−
1
N-1
N−1條邊;
.
判斷一個方案是否生成樹的做法是: 用一顆並查集 對他的每條無向邊a-b
放到並查集裡, 最後判斷 所有點是否在同一個並查集裡面;
優化: 直接枚舉
C
M
N
−
1
C_M^{N-1}
CMN−1 改成是DFS, 因為DFS存在優化的空間 比如此時你所選擇的邊集合 已經是非法的了(即存在環) 那麼就可以直接return
了; 而且 用一個全局的 支持撤銷操作的 並查集, 來動態的維護 當前所選擇的邊集合(即生成樹)的狀態;
時間是
C
M
N
−
1
C_M^{N-1}
CMN−1;
.
對於
8
8
8個點的完全圖, 他是
C
8
∗
7
/
2
7
=
1184040
C_{8*7/2}^7 = 1184040
C8∗7/27=1184040; 對於
9
9
9個點的完全圖, 他是
C
9
∗
8
/
2
8
=
30260340
C_{9*8/2}^8 = 30260340
C9∗8/28=30260340;
@DELI;
代碼
namespace ___Get_AllSpanningTrees{
vector< tuple<int,int,int64_t> > __Edges; // 其元素`(a,b)`表示一條*無向邊*, 你自己要確保: 他是一個合格的*連通無向圖*, 比如`[(0-1),(1-2),(1-2)]`是合格的;
int __PointsCount; // `Edges`裡的所有點 都是`[0, PointsCount)`範圍;
vector<int> __ANS; // 表示一個生成樹; 比如`PoinsCount=3`, 那麼`__ANS=[4,8]` 且`Edges[4]=(0,1), Edges[8]=(0,2)` 表示一個生成樹;
___DisjointSet_Undo __DSet;
void Initialize( int _pointsCount){
__PointsCount = _pointsCount;
__Edges.clear();
__DSet.Initialize( __PointsCount);
}
void AddEdge( int _a, int _b, int64_t _w){
ASSERT_WEAK_( 0<=_a && _a<__PointsCount && 0<=_b && _b<__PointsCount);
__Edges.emplace_back( _a, _b, _w);
}
void __Dfs( int _edgeInd){
if( (int)__ANS.size() == __PointsCount-1){
@TODO( `__ANS`就是一個生成樹);
return;
}
if( _edgeInd == (int)__Edges.size()){ return;}
auto a = std::get<0>( __Edges[_edgeInd]), b = std::get<1>( __Edges[_edgeInd]);
if( __DSet.GetRoot(a) != __DSet.GetRoot(b)){
__DSet.Merge( a, b); __ANS.push_back(_edgeInd);
__Dfs( _edgeInd+1);
__DSet.Undo(); __ANS.pop_back();
}
__Dfs( _edgeInd+1);
}
void Work(){
__Dfs( 0);
}
} // namespace ___Get_AllSpanningTrees
例题
@LINK: https://atcoder.jp/contests/abc328/tasks/abc328_e
;