正睿省选模拟赛:两弹一星(组合数学,斯特林数,dp)

本文探讨了一个复杂的图论问题,即计算n个点的所有无向图的权值之和,其中权值由联通块为树的数量决定。通过使用动态规划和NTT优化,算法将复杂度降低至O(nklogn),并利用斯特林数展开求解∑iik。代码实现展示了如何高效地解决这一问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目大意:
对于一个无向图GGG,假设GGG中有xxx个联通块为树,那么GGG的权值就为xkx^kxkkkk给定)
给定n,kn,kn,knnn个点的所有无向图的权值之和
998244353998244353998244353取模
n≤3∗104,k≤20n\le 3*10^4,k\le 20n3104,k20


solutionsolutionsolution
考虑一个n3n^3n3暴力,f[i][j]f[i][j]f[i][j]表示iii个点,构成jjj棵树的方案数
再统计一下xxx个点没有树联通块的方案树就可以统计答案了
dpdpdp的过程可以NTTNTTNTT优化到n2lognn^2lognn2logn

∑iik\sum_i i^kiik可以很容易的想到用斯特林数展开

考虑一个图GGG的贡献,假设他有xxx个联通块为树
xk=∑j{kj}xj‾=∑j{kj}Cxj∗j! x^k=\sum_{j} \left\{ {k\atop j} \right\} x^{\underline j}\\ =\sum_j \left\{ {k\atop j} \right\} C_x^j * j! xk=j{jk}xj=j{jk}Cxjj!
考虑一下这个式子
CxjC_x^jCxj相当于是从这xxx个联通块中挑出jjj个联通块,那就意味着我们可以把xxx的贡献摊到所有大小为j≤kj\le kjk的联通块上。
这样对于所有的图我们就可以一起算贡献了

考虑复杂度,f[i][j]f[i][j]f[i][j]第二位只要求到kkk
复杂度O(nklogn)O(nklogn)O(nklogn)

#include<bits/stdc++.h>
using namespace std;
#define rep(i,j,k) for(int i = j;i <= k;++i)
int rd()
{
	int num = 0;char c = getchar();bool flag = true;
	while(c < '0'||c > '9') {if(c == '-') flag = false;c = getchar();}
	while(c >= '0' && c <= '9') num = num*10+c-48,c = getchar();
	if(flag) return num;else return -num;
}
const int p = 998244353,g = 3;
int n,k;
int f[30100][25],s[25][25];
int fac[30100],inv[30100],flen,inv_n;
int a[120100],b[120100],r[120100],w[120100];
inline int calc(int a,int b){return (a+=b)>=p?a-=p:a;}
inline int mul(int a,int b){return 1ll*a*b%p;}
inline int del(int a,int b){return (a-=b)<0?a+=p:a;}
inline int ksm(int a,int x){int now = 1;for(;x;x>>=1,a=1ll*a*a%p)if(x&1)now=1ll*now*a%p;return now;}
inline int min(int a,int b){return a<b?a:b;}
inline void swap(int &a,int &b){a^=b;b^=a;a^=b;}
inline int C(int i,int j){
    if(j > i) return 0;
    return (j==0||j==i)?1:mul(fac[i],mul(inv[j],inv[i-j]));
}
void fft(int *a,int f)
{
	w[0] = 1;
	rep(i,1,flen-1) if(i < r[i]) swap(a[i],a[r[i]]);
	for(int len = 2;len <= flen;len <<= 1)
	{
		int wn = ksm(g,(p-1)/len);
		if(f == 1) wn = ksm(wn,p-2);
		rep(i,1,len/2) w[i] = mul(w[i-1],wn);
		for(int st = 0;st < flen;st += len)
			rep(i,0,len/2-1)
			{
				int x = a[st+i],y = mul(w[i],a[st+i+len/2]);
				a[st+i] = calc(x,y);
				a[st+i+len/2] = del(x,y);
			}
	}
}
void work()
{
	rep(i,n,flen-1) a[i] = b[i] = 0;
	fft(a,1);fft(b,1);
	rep(i,0,flen-1) a[i] = mul(a[i],b[i]);
	fft(a,-1);
	rep(i,0,flen-1) a[i] = mul(a[i],inv_n);
}
int main()
{
	n = rd();k = rd();flen = 1;
	while(flen < 2*n-1) flen <<= 1;  inv_n = ksm(flen,p-2);
	rep(i,0,flen-1) r[i] = (r[i>>1]>>1) | ((i&1)?flen/2:0);
	s[0][0] = 1;  fac[0] = inv[0] = 1;
	rep(i,1,k) rep(j,1,i) s[i][j] = calc(s[i-1][j-1],mul(j,s[i-1][j]));
	rep(i,1,n) fac[i] = mul(fac[i-1],i),inv[i] = ksm(fac[i],p-2);
	rep(i,1,n) f[i][1] = i<2?1:ksm(i,i-2);
	rep(j,2,k)
	{ 
		rep(i,0,n-1) a[i] = mul(f[i+1][1],inv[i]);
		rep(i,0,n-1) b[i] = mul(f[i+1][j-1],inv[i+1]);
		work();
		rep(i,j,n) f[i][j] = mul(a[i-2],fac[i-1]);
	}
	int ans = 0;
	rep(i,1,n)
		rep(j,1,k)
			ans = calc(ans , mul( f[i][j] , mul( ksm(2,C(n-i,2)) , mul( C(n,i) , mul( fac[j] , s[k][j] ) ) ) ) );
	printf("%d\n",ans);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值