[北京省选集训2019]生成树计数

Posted crashed

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[北京省选集训2019]生成树计数相关的知识,希望对你有一定的参考价值。

题目

点这里看题目。

分析

考察一下矩阵树定理的基本式子:

[sum_T prod_{ein T} w_e ]

(v(T))(T)的权值,我们发现,(v(T))应该是(T)中的边的“某种意义”下的

这意味着,我们只需要能够保证(v(T))的贡献可分割,便可以定义一个存在基础四则运算的“类型”,满足该“类型”的积就相当于合并了边的贡献。利用该“类型”,我们就可以用矩阵树定理计算所有生成树的贡献。

例如,一般矩阵树定理,利用(带模)整数的运算即可;

再例如,求边权和的矩阵树定理,利用“一次函数”的“积”,我们求出了系数的“和”。

对于此题,我们考虑将“合并贡献”作为切入点,即考虑已知(a^k,b^k),如何求出((a+b)^k)

当然这样没法做,我们考虑用二项式定理展开:

[(a+b)^k=sum_{i=0}^kinom{k}{i}a^ib^{k-i} ]

这样需要用到较低次幂,不过没有关系,我们同样可以维护一下较低次幂,合并方法类似。

仔细观察你会发现这是指数生成函数的卷积,不过在这道题里面没有用。

然后我们只需要用长为(k+1)的向量(vec{z}=egin{bmatrix}a^0&a^1&a^2&dots&a^kend{bmatrix})表示贡献即可。

加法和减法:不再赘述。

乘法:“二项式卷积”。

求逆元:可以次数从低到高进行递推,不再赘述。

单次乘法(O(k^2)),总时间复杂度(O(n^3k^2))

代码

#include <cstdio>
#include <iostream>

const int mod = 998244353;
const int MAXN = 35;

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‘ );
}

int C[MAXN][MAXN], invC[MAXN][MAXN];
int K;

int inv( const int );
int qkpow( int, int );
void add( int&, const int );
void sub( int&, const int );

struct vec
{
	int num[MAXN] = {};
	
	//h(i)=??{j=0...n}f(j)*g(i-j)*C(i,j) 
	
	vec() {}
	vec( const int b ) { num[0] = b; }
	
	int& operator [] ( const int indx ) { return num[indx]; }
	
	vec inver() const
	{
		vec ret;
		ret[0] = inv( num[0] );
		for( int i = 1, tmp ; i <= K ; i ++ )
		{
			tmp = 0;
			for( int j = 0 ; j < i ; j ++ )
				add( tmp, 1ll * ret[j] * num[i - j] % mod * C[i][j] % mod );
			ret[i] = 1ll * ( mod - tmp ) % mod * ret[0] % mod;
		}
		return ret;
	}
	
	vec operator + ( vec g ) const
	{
		vec ret;
		for( int i = 0 ; i <= K ; i ++ )
			add( ret[i], num[i] ), add( ret[i], g[i] );
		return ret;
	}
	
	vec operator - ( vec g ) const 
	{
		vec ret;
		for( int i = 0 ; i <= K ; i ++ )
			add( ret[i], num[i] ), sub( ret[i], g[i] );
		return ret;
	}

	vec operator * ( vec g ) const 
	{
		vec ret;
		for( int i = 0 ; i <= K ; i ++ )
			for( int j = 0 ; j <= i ; j ++ )
				add( ret[i], 1ll * num[j] * g[i - j] % mod * C[i][j] % mod );
		return ret;
	}
	
	vec operator / ( vec g ) const { return g * inver(); }
	void operator += ( vec g ) { *this = *this + g; }
	void operator -= ( vec g ) { *this = *this - g; }
	void operator *= ( vec g ) { *this = *this * g; }
	void operator /= ( vec g ) { *this = *this / g; }

	operator bool() const 
	{
		bool ret = false;
		for( int i = 0 ; i <= K ; i ++ )
			ret |= num[i]; 
		return ret; 
	} 
};

vec D[MAXN][MAXN], G[MAXN][MAXN], Kich[MAXN][MAXN];
int N;

int qkpow( int base, int indx )
{
	int ret = 1;
	while( indx )
	{
		if( indx & 1 ) ret = 1ll * ret * base % mod;
		base = 1ll * base * base % mod, indx >>= 1;
	}
	return ret;
}

int inv( const int a ) { return qkpow( a, mod - 2 ); }
void sub( int &x, const int v ) { x = ( x < v ? x - v + mod : x - v ); }
void add( int &x, const int v ) { x = ( x + v >= mod ? x + v - mod : x + v ); }

int det( vec T[][MAXN], const int n )
{
	vec ans = 1, tmp, inver; int indx;
	for( int i = 1 ; i <= n ; i ++ )
	{
		indx = -1;
		for( int j = i ; j <= n ; j ++ )
			if( T[j][i] ) 
			{ indx = j; break; }
		if( indx == -1 ) return 0;
		if( indx ^ i ) 
			for( int k = 0 ; k <= K ; k ++ )
				ans[k] = mod - ans[k];
		std :: swap( T[i], T[indx] );
		inver = T[i][i].inver();
		for( int j = i + 1 ; j <= n ; j ++ )
			if( T[j][i] )
			{
				tmp = T[j][i] * inver;
				for( int k = i ; k <= n ; k ++ )	
					T[j][k] -= T[i][k] * tmp;
			}
		ans *= T[i][i];
	}
	return ans[K];
}

int main()
{
	int w;
	read( N ), read( K );
	for( int i = 0 ; i <= K ; i ++ )
	{
		C[i][0] = C[i][i] = 1;
		for( int j = 1 ; j < i ; j ++ )
			add( C[i][j], C[i - 1][j] ), add( C[i][j], C[i - 1][j - 1] );
	}
	int t;
	for( int i = 1 ; i <= N ; i ++ )
		for( int j = 1 ; j <= N ; j ++ )
		{
			read( w ), t = 1;
			for( int k = 0 ; k <= K ; k ++ )
				G[i][j][k] = t, t = 1ll * t * w % mod;
		}
	for( int i = 1 ; i <= N ; i ++ )
		for( int j = 1 ; j <= N ; j ++ )
			D[i][i] += G[i][j];
	for( int i = 1 ; i <= N ; i ++ )
		for( int j = 1 ; j <= N ; j ++ )
			Kich[i][j] = D[i][j] - G[i][j];
	write( det( Kich, N - 1 ) ), putchar( ‘
‘ );
	return 0;
}

以上是关于[北京省选集训2019]生成树计数的主要内容,如果未能解决你的问题,请参考以下文章

2017雅礼省选集训做题记录

HNOI2015省选集训题

省选集训做题记录

兔子的晚会 2016Vijos省选集训 day1

股神小L 2016Vijos省选集训 day1

怎么求一幅图像的最小生成树