zoukankan      html  css  js  c++  java
  • 最小生成树计数

    方法

    最小生成树上有一个重要的性质:

    [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;
    }
    
  • 相关阅读:
    linux下ping命令出现ping: sendto: Network is unreachable
    tiny4412--linux驱动学习(2)
    tiny4412--linux驱动学习(1)
    linux-kernel-4.4 移植 (2)解决上部遗留DMA-PL330的问题
    linux-kernel-4.4 移植 (1)启动
    Busybox构建根文件系统和制作Ramdisk
    tiny4412 --Uboot移植(6) SD卡驱动,启动内核
    select响应事件
    项目总结1
    一个盒子只是显示两行
  • 原文地址:https://www.cnblogs.com/crashed/p/13207518.html
Copyright © 2011-2022 走看看