用户工具

站点工具


2020-2021:teams:legal_string:组队训练比赛记录:缓冲区

这是本文档旧的修订版!


D. Diameter Counting

题意

求所有 $n$ 标号树的直径和。

题解1

考虑求树的直径的过程,可以先删去所有叶子结点,得到一棵新树,称为一次操作。然后再不断对新树进行操作,知道最后剩下一个或两个结点。

此时如果只剩下一个结点,则树的直径为操作次数 $\times 2$。如果剩下两个结点,则树的直径为操作次数 $\times 2+1$。

考虑通过逆算法反向构建树。设 $f(i,j)$ 表示有 $j$ 个叶子结点的 $i$ 标号树个数,假设上一步操作删除了 $k(k\ge j)$ 个叶子。

于是问题等价于给这 $k$ 个叶子找一个父结点,使得原来的 $j$ 个叶子结点至少有一个儿子。

同时对于这 $i+k$ 个结点,标号是任意的,对所有 $i+k$ 标号树而言,删去 $k$ 叶子结点得到的树的标号方式实际上有 ${i+k\choose i}f(i,j)$ 种。

设 $g(i,j,k)$ 表示长度为 $k$ 且每个位置有 $i$ 种可选取值且特定的 $j$ 个值至少出现一次的序列个数,于是有

$$ f(i+k,k)\gets g(i,j,k)f(i,j){i+k\choose i} $$

接下来考虑求 $g(i,j,k)$,可以考虑序列前 $k-1$ 位,如果此时 $j$ 个特定值都出现了至少一次,则第 $k$ 位可以任取,于是有

$$ g(i,j,k)\gets i\times g(i,j,k-1) $$

如果前 $k-1$ 位只有 $j-1$ 个特殊值出现了至少一次,则显然前 $k-1$ 位的取值只有 $i-1$ 种,同时要从 $j$ 个特殊值中确定一个放在第 $k$ 位,有

$$ g(i,j,k)\gets j\times g(i-1,j-1,k-1) $$

最后设 $h(i,j)$ 表示有 $j$ 个叶子的 $i$ 标号树的直径之和。同样假设上一步操作删除了 $k(k\ge j)$ 个叶子。

考虑原有的树的直径和操作带来的直径 $+2$ 的新贡献,于是有

$$ h(i+k,k)\gets g(i,j,k){i+k\choose i}(h(i,j)+2f(i,j)) $$

另外所有 $h(i+k,k)\gets 2f(i,j)g(i,j,k){i+k\choose i}$ 也可以等价于 $h(i,j)\gets 2f(i,j)$。时空间复杂度 $O\left(n^3\right)$。

查看代码

查看代码

const int MAXN=505;
int mod;
int quick_pow(int n,int k){
	int ans=1;
	while(k){
		if(k&1)ans=1LL*ans*n%mod;
		n=1LL*n*n%mod;
		k>>=1;
	}
	return ans;
}
int frac[MAXN],invf[MAXN];
int C(int n,int m){
	return 1LL*frac[n]*invf[m]%mod*invf[n-m]%mod;
}
int f[MAXN][MAXN],g[MAXN][MAXN][MAXN],h[MAXN][MAXN];
int main()
{
	int n=read_int();
	mod=read_int();
	frac[0]=1;
	_for(i,1,MAXN)frac[i]=1LL*frac[i-1]*i%mod;
	invf[MAXN-1]=quick_pow(frac[MAXN-1],mod-2);
	for(int i=MAXN-1;i;i--)
	invf[i-1]=1LL*invf[i]*i%mod;
	g[0][0][0]=1;
	_rep(i,1,n){
		g[i][0][0]=1;
		_rep(k,1,n)
		g[i][0][k]=1LL*g[i][0][k-1]*i%mod;
		_rep(j,1,i)_rep(k,j,n)
		g[i][j][k]=(1LL*g[i][j][k-1]*i+1LL*g[i-1][j-1][k-1]*j)%mod;
	}
	f[1][1]=f[2][2]=1;
	_rep(i,1,n)_rep(j,1,i)_rep(k,max(j,2),n-i)
	f[i+k][k]=(f[i+k][k]+1LL*f[i][j]*g[i][j][k]%mod*C(i+k,i))%mod;
	h[2][2]=1;
	_rep(i,3,n)_rep(j,1,i)
	h[i][j]=2LL*f[i][j]%mod;
	_rep(i,1,n)_rep(j,1,i)_rep(k,max(j,2),n-i)
	h[i+k][k]=(h[i+k][k]+1LL*h[i][j]*g[i][j][k]%mod*C(i+k,i))%mod;
	int ans=0;
	_rep(i,1,n)
	ans=(ans+h[n][i])%mod;
	enter(ans);
	return 0;
}

题解2

设 $f(i,j)$ 表示深度为 $j$ 的 $i$ 标号树个数,$g(i,j)$ 表示深度不超过 $j$ 的 $i$ 标号树个数。

对一个直径为 $2d+1$ 的 $n$ 标号无根树,可以沿着直径的中心边切开,得到两个深度为 $d$ 的有根树。

设 $1$ 号点所在的有根树大小为 $i$,考虑为他分配 $i-1$ 个编号,于是直径为 $2d+1$ 的 $n$ 标号无根树个数为

$$ \sum_{i=1}^{n-1}f(i,d)f(n-i,d){n-1\choose i-1} $$

对一个直径为 $2d$ 的 $n$ 标号无根树,可以认为是根节点连接至少两个深度等于 $d-1$ 的有根树。

利用容斥,用深度为 $d$ 的 $n$ 标号有根树个数减去根节点仅连接一个深度为 $d-1$ 的有根树个数的情况。

根节点仅连接一个深度为 $d-1$ 的有根树可以认为是由一个深度不超过 $d-1$ 的有根树根节点连接一个深度等于 $d-1$ 的有根树得到的。

于是直径为 $2d$ 的 $n$ 标号无根树个数为

$$ f(n,d)-\sum_{i=1}^{n-1}f(i,d-1)g(n-i,d-1){n\choose i} $$

接下来考虑计算 $f(i,j),g(i,j)$。$f(i,j)$ 难以直接计算,但显然有 $f(i,j)=g(i,j)-g(i,j-1)$,于是只需要计算 $g(i,j)$。

对于深度不超过 $j$ 的 $i$ 标号树个数,可以先分配一个编号给根结点,然后从与根节点相连的子树中找到编号最小的结点所在的子树。

设子树大小为 $k$,对该子树,他深度不超过 $j-1$,显然有 $g(k,j-1)$ 种。另外需要从剩余 $i-2$ 个编号再分配 $k-1$ 个编号给子树。

对于余下的 $n-k$ 个点,深度仍然不超过 $j$,但根节点编号已经分配,所以总数为 $\frac {g(i-k,j)}{i-k}$。于是有

$$ g(i,j)=\sum_{k=1}^{i-1}i{i-2\choose k-1}g(k,j-1)\frac {g(i-k,j)}{i-k} $$

时间复杂度 $O\left(n^3\right)$,空间复杂度 $O\left(n^2\right)$。

查看代码

查看代码

const int MAXN=505;
int mod;
int quick_pow(int n,int k){
	int ans=1;
	while(k){
		if(k&1)ans=1LL*ans*n%mod;
		n=1LL*n*n%mod;
		k>>=1;
	}
	return ans;
}
int frac[MAXN],invf[MAXN],inv[MAXN];
int C(int n,int m){
	return 1LL*frac[n]*invf[m]%mod*invf[n-m]%mod;
}
int f[MAXN][MAXN],g[MAXN][MAXN];
int main()
{
	int n=read_int();
	mod=read_int();
	frac[0]=1;
	_for(i,1,MAXN)frac[i]=1LL*frac[i-1]*i%mod;
	invf[MAXN-1]=quick_pow(frac[MAXN-1],mod-2);
	for(int i=MAXN-1;i;i--){
		invf[i-1]=1LL*invf[i]*i%mod;
		inv[i]=1LL*invf[i]*frac[i-1]%mod;
	}
	g[1][0]=1;
	_rep(i,1,n){
		_for(j,1,i)_for(k,1,i)
		g[i][j]=(g[i][j]+1LL*g[k][j-1]*g[i-k][j]%mod*C(i-2,k-1)%mod*i%mod*inv[i-k])%mod;
		_rep(j,i,n)
		g[i][j]=g[i][i-1];
	}
	f[1][0]=1;
	_rep(i,2,n)_for(j,1,i)
	f[i][j]=(g[i][j]-g[i][j-1])%mod;
	int ans=0;
	_for(i,1,n){
		int cnt=0,d=i/2;
		if(i&1){
			_for(j,1,n)
			cnt=(cnt+1LL*f[j][d]*f[n-j][d]%mod*C(n-1,j-1))%mod;
		}
		else{
			cnt=f[n][d];
			_for(j,1,n)
			cnt=(cnt-1LL*f[j][d-1]*g[n-j][d-1]%mod*C(n,j))%mod;
		}
		ans=(ans+1LL*cnt*i)%mod;
	}
	if(ans<0)ans+=mod;
	enter(ans);
	return 0;
}
2020-2021/teams/legal_string/组队训练比赛记录/缓冲区.1629277690.txt.gz · 最后更改: 2021/08/18 17:08 由 jxm2001