方法
最小生成树上有一个重要的性质:
[egin{aligned}
&w_e: e ext{的权}\
& ext{Tree}(G): G ext{的生成树集合}\
&f(T,w) riangleq sum_{ein T}[w_e=w]\
Rightarrow& forall T_1,T_2in ext{Tree}(G),forall w, f(T_1,w)=f(T_2,w)
end{aligned}
]
翻译成人话就是,同一个图上任意两棵最小生成树,指定任意权值,两棵树上边权等于这个权值的边的数量都是相等的。
道理非常简单。对于一条非树边,它一定对应了树上的一条链,这条链上最大边权一定小于等于这条边的权。如果相等,我们可以替换,得到一棵新的最小生成树。
然后我们就只需要考虑相同权值的边的情况,最终答案就是所有权值的方案数的积。
按照权值从小到大。取出当前权值的所有边,得到边集(E')。假如我们将连通块缩成点,得到新点集(V')。定义新图(G'=(V',E')),那么我们能且仅能在保证不连成环的情况下取尽可能多的边。可以保证这样取出来一定会得到一棵最小生成树(因为这就是 Kruskal 的过程)。计算这样取的方案数我们就得到了当前阶段的方案数。
然后我们加入(E')中的所有边并缩点,进入下一阶段计算。
当相同权值的边的数量较小时,我们可以直接枚举计算(比如),否则需要用 Matrix-Tree 计算。
例题
[JSOI2008]最小生成树计数,相同权值的边比较少,所以直接枚举计算每个阶段的贡献。
代码见下:
#include <cstdio>
#include <cstring>
#include <algorithm>
const int mod = 31011;
const int MAXN = 105, MAXE = 1005;
template<typename _T>
void read( _T &x )
{
x = 0;char s = getchar();int f = 1;
while( s > '9' || s < '0' ){if( s == '-' ) f = -1; s = getchar();}
while( s >= '0' && s <= '9' ){x = ( x << 3 ) + ( x << 1 ) + ( s - '0' ), s = getchar();}
x *= f;
}
template<typename _T>
void write( _T x )
{
if( x < 0 ){ putchar( '-' ); x = ( ~ x ) + 1; }
if( 9 < x ){ write( x / 10 ); }
putchar( x % 10 + '0' );
}
struct edge
{
int u, v, w;
edge() { u = v = w = 0; }
edge( const int U, const int V, const int W ) { u = U, v = V, w = W; }
bool operator < ( const edge & b ) const { return w < b.w; }
}E[MAXE];
struct dsu
{
int fa[MAXN] = {}, s;
dsu() { s = 0, memset( fa, 0, sizeof fa ); }
dsu( const int n ) { s = n; for( int i = 1 ; i <= s ; i ++ ) fa[i] = i; }
dsu( const dsu &t ) { s = t.s; for( int i = 1 ; i <= s ; i ++ ) fa[i] = t.fa[i]; }
int findSet( const int u ) { return fa[u] = ( fa[u] == u ? u : findSet( fa[u] ) ); }
bool unionSet( int u, int v ) { u = findSet( u ), v = findSet( v ); fa[u] = v; return u ^ v; }
}cur;
int N, M;
int main()
{
read( N ), read( M );
for( int i = 1 ; i <= M ; i ++ )
read( E[i].u ), read( E[i].v ), read( E[i].w );
std :: sort( E + 1, E + 1 + M );
cur = dsu( N ); int ans = 1;
for( int l = 1, r ; l <= M ; )
{
for( r = l ; r <= M && E[r].w == E[l].w ; r ++ );
int siz = r - l, cnt = 0, tot, mx = 0;
for( int S = 0 ; S < 1 << siz ; S ++ )
{
dsu tmp( cur ); tot = 0;
for( int i = 0 ; i < siz ; i ++ )
if( S & ( 1 << i ) )
{
if( ! tmp.unionSet( E[i + l].u, E[i + l].v ) ) { tot = -1; break; }
tot ++;
}
if( tot > mx ) mx = tot, cnt = 1;
else if( tot == mx ) cnt ++;
}
ans = 1ll * ans * cnt % mod;
for( ; l < r ; l ++ ) cur.unionSet( E[l].u, E[l].v );
}
int cnt = 0;
for( int i = 1 ; i <= N ; i ++ ) cnt += cur.fa[i] == i;
if( cnt > 1 ) puts( "0" );
else write( ans ), putchar( '
' );
return 0;
}