烷基计数问题

烷基计数加强加强版

题目描述:求每个节点儿子个数不超过 \(3\)\(n\) 个点的无标号有根树的个数,对 \(998244353\) 取模。

数据范围:$ 1 \leq n \leq 10^5 $。


orz 王总

考虑 DP,设 $ f_n $ 表示 $ n$ 个节点时的答案。显然 $ f_0 = 1 $。假设我们已经求出了 \(f_1,f_2,\cdots,f_{n-1}\),现在要求 $ f_{n} $。如果根节点的每个子树互不相同的话,那么显然有:

\[f_n=\sum_{i+j+k=n-1}f_if_jf_k \]

但由于节点是无标号的,因此考虑 \(\text{Burnside}\) 引理。分三种情况讨论:

  1. 恒等置换,即 \(\begin{pmatrix}1&2&3\\1&2&3\end{pmatrix}\),此时不动点数量就是上面的式子。
  2. 交换两个子树,即 \(\begin{pmatrix}1&2&3\\2&1&3\end{pmatrix}\)\(\begin{pmatrix}1&2&3\\1&3&2\end{pmatrix}\)\(\begin{pmatrix}1&2&3\\3&2&1\end{pmatrix}\),此时不动点数量为 $ \sum\limits_{2i+j=n-1}f_if_j$。
  3. 三个子树轮换,即 \(\begin{pmatrix}1&2&3\\2&3&1\end{pmatrix}\)\(\begin{pmatrix}1&2&3\\3&1&2\end{pmatrix}\),此时不动点数量为 \(\sum\limits_{3i=n-1}f_i\)

综上,可得转移式:

\[f_n=\frac{1}{6}(\sum_{i+j+k=n-1}f_if_jf_k+3\sum_{2i+j=n-1}f_if_j+2\sum_{3i=n-1}f_i) \]

直接计算时间复杂度 \(O(n^3)\)。若边算边处理 $ g_n=\sum\limits_{i+j=n}f_if_j$ 可将时间复杂度优化至 $ O(n^2) $,加上一些多项式科技后时间复杂度为 $ O(n \log^2 n)$ 或 $ O(n \log n) $。

下面为 $ O(n^2)$ 的代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
inline int read(){
	int f=1,r=0;char c=getchar();
	while(!isdigit(c))f^=c=='-',c=getchar();
	while(isdigit(c))r=(r<<1)+(r<<3)+(c&15),c=getchar();
	return f?r:-r;
}
const int N=5007,mod=998244353,inv6=166374059;
inline void inc(int &x,int y){x+=y-mod,x+=x>>31&mod;}
int n,f[N],g[N];
int main(){
#ifndef ONLINE_JUDGE
	freopen("1.in","r",stdin);
	freopen("1.out","w",stdout);
#endif
	n=read(),f[0]=1;
	for(int i=0;i<n;i++){
		for(int j=0;j<=i;j++)
			g[i]=(g[i]+(ll)f[j]*f[i-j])%mod;
		int res=0;
		for(int j=0;j<=i;j++)
			res=(res+(ll)g[j]*f[i-j])%mod;
		inc(f[i+1],res),res=0;
		for(int j=0;2*j<=i;j++)
			res=(res+(ll)f[j]*f[i-2*j])%mod;
		inc(f[i+1],3ll*res%mod);
		if(i%3==0)inc(f[i+1],2ll*f[i/3]%mod);
		f[i+1]=(ll)f[i+1]*inv6%mod;
	}
	printf("%d\n",f[n]);
	return 0;
}

[JSOI2011]同分异构体计数

题目描述:求每个点度数不超过 \(4\) 的 $ n$ 个点的无标号基环树个数,环长 $ \in[3,m]$,对质数 $ p $ 取模。

数据范围:\(1 \leq n \leq 1000,1 \leq m \leq 50,m \leq n,10^4 \leq p \leq 2 \times 10^9\)


可以发现环上的点最多能连 \(2\) 个烷基(就是上面那题求的东西),于是设 $ g_n $ 表示一个环上的点向外连 $ n-1$ 个点的方案数。这里分两种情况讨论:

  1. 恒等置换,即 \(\begin{pmatrix}1&2\\1&2\end{pmatrix}\),此时不动点数量为 \(\sum\limits_{i+j=n-1}f_if_j\)
  2. 交换两个烷基,即\(\begin{pmatrix}1&2\\2&1\end{pmatrix}\),此时不动点数量为 $ \sum\limits_{2i=n-1}f_i$。

因此可得:

\[g_n=\frac{1}{2}(\sum_{i+j=n-1}f_if_j+\sum_{2i=n-1}f_i) \]

再考虑求总方案数。先枚举环长 $ k \in [3,m]$。同样使用 $ \text{Burnside} $ 引理,有两种类型的置换:

  1. 循环置换,即 $ p_i'=(p_i+a) \bmod k \ (a\in[0,k)\ )$,此时循环节大小 $ s = \gcd(a,k) $,个数 $ t=\dfrac{k}{s}$,方案数为:

    \[\sum_{t\sum\limits_{i=1}^s b_i=n}\prod_{i=1}^s g_{b_i} \]

  2. 沿对称轴翻转的置换,即 $ p_i'=(a-p_i) \bmod k \ (a \in [0,k) \ )$,再分两种情况讨论:

    (1) $ k \equiv 0 \pmod 2$,此时有一半的情况每个点均有唯一一个与该点对称点,一半的情况有两个点的对称点是本身。此时方案数分别为:

    \[\sum_{2\sum\limits_{i=1}^{k/2}b_i=n}\prod_{i=1}^{k/2}g_{b_i},\sum_{b_0+b_{1}+2\sum\limits_{i=2}^{k/2}b_i=n}\prod_{i=0}^{k/2}g_{b_i} \]

    (2) $ k \equiv 1 \pmod 2$,此时只有一个点的对称点是本身,此时方案数为:

    \[\sum_{b_0+2\sum\limits_{i=1}^{k/2}b_i=n}\prod_{i=0}^{k/2}g_{b_i} \]

因此,$ ans_k$ 就是把上面两种情况加起来除以 \(2k\) 就行了。式子中的 \(\sum\limits_{\sum\limits_{i=1}^mb_i=n}\prod_{i=1}^mg_{b_{i}}\) 可以 $ O(n^2m)$ 预处理。

总时间复杂度为 $ O(n^2 m+m^2\log m)$。

代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
inline int read(){
	int f=1,r=0;char c=getchar();
	while(!isdigit(c))f^=c=='-',c=getchar();
	while(isdigit(c))r=(r<<1)+(r<<3)+(c&15),c=getchar();
	return f?r:-r;
}
const int N=1007,M=57;
int inv2,inv6,mod;
int gcd(int x,int y){return y?gcd(y,x%y):x;}
inline void inc(int &x,int y){x+=y-mod,x+=x>>31&mod;}
inline void dec(int &x,int y){x-=y,x+=x>>31&mod;}
inline int qpow(int a,int b){
	int res=1;
	for(;b;b>>=1,a=(ll)a*a%mod)
		if(b&1)res=1ll*res*a%mod;
	return res;
}
int n,m,f[N],g[N],h[M][N];
inline void init(){
	inv2=qpow(2,mod-2),inv6=qpow(6,mod-2),f[0]=1;
	for(int i=0;i<n;i++){
		for(int j=0;j<=i;j++)
			g[i]=(g[i]+(ll)f[j]*f[i-j])%mod;
		int res=0;
		for(int j=0;j<=i;j++)
			res=(res+(ll)g[j]*f[i-j])%mod;
		inc(f[i+1],res),res=0;
		for(int j=0;2*j<=i;j++)
			res=(res+(ll)f[j]*f[i-2*j])%mod;
		inc(f[i+1],3ll*res%mod);
		if(i%3==0)inc(f[i+1],2ll*f[i/3]%mod);
		f[i+1]=(ll)f[i+1]*inv6%mod;
	}
}
int main(){
#ifndef ONLINE_JUDGE
	freopen("1.in","r",stdin);
	freopen("1.out","w",stdout);
#endif
	n=read(),m=read(),mod=read(),init(),g[0]=0;
	for(int i=1;i<=n;i++){
		g[i]=i&1?f[i/2]:0;
		for(int j=0;j<i;j++)
			g[i]=(g[i]+(ll)f[j]*f[i-j-1])%mod;
		g[i]=(ll)g[i]*inv2%mod;
	}
	h[0][0]=1;
	for(int i=1;i<=m;i++)
		for(int j=1;j<=n;j++)
			for(int k=0;k<j;k++)
				h[i][j]=(h[i][j]+(ll)h[i-1][k]*g[j-k])%mod;
	int ans=0;
	for(int k=3;k<=m;k++){
		int res=0;
		for(int a=0;a<k;a++){
			int s=gcd(a,k),t=k/s;
			if(n%t==0)inc(res,h[s][n/t]);
		}
		if(k&1){
			int tmp=0;
			for(int i=1;i<=n-k+1;i++)
				if((n-i)%2==0)inc(tmp,(ll)g[i]*h[k>>1][(n-i)>>1]%mod);
			inc(res,(ll)k*tmp%mod);
		}else{
			if(n%2==0)inc(res,(ll)(k>>1)*h[k>>1][n>>1]%mod);
			int tmp=0;
			for(int i=1;i<=n-k+1;i++)
				for(int j=1;i+j<=n-k+2;j++)
					if((n-i-j)%2==0)inc(tmp,(ll)g[i]*g[j]%mod*h[k/2-1][(n-i-j)>>1]%mod);
			inc(res,(ll)k/2*tmp%mod);
		}
		ans=(ans+(ll)res*qpow(k<<1,mod-2))%mod;
	}
	printf("%d\n",ans);
	return 0;
}
posted @ 2022-06-26 20:03  b1ts  阅读(177)  评论(0编辑  收藏  举报