其他分享
首页 > 其他分享> > P6598 烷烃计数(Burside引理/无根树转有根树/动态规划)

P6598 烷烃计数(Burside引理/无根树转有根树/动态规划)

作者:互联网

P6598 烷烃计数

求解度数小于等于4的n个点的无根树个数

发现对于任意无根树有p-q+s=1,p是点等价类个数,q是边等价类个数,s是[存在两个重心]

考虑分类讨论证明:

当s=0时,任意选择一个重心作为根,那么每个等价的点上面的父亲边一定是等价的,然后根节点没有父亲,所以p=q+1

当s=1时,将两个重心看作整体,每个等价的点上面的父亲边一定是等价的,然后两个重心等价,它们对应着二者之间的边,所以p=q

那么考虑求解\(\sum p-\sum q+\sum s=ans\)然后\(\sum p\)就是本质不同有根树的个数, \(\sum q\)就是一条边连接两边两个大小之和为n的子树的方案数,\(\sum s\)就是一条边两边子树相同的方案数,这样我们就成功将无根树问题转化为有根树问题了,那就至少可以使用dynamic programing了。

然后先计算儿子个数小于等于3的有根树个数设生成函数为\(A(x)\)

设\(f_n\)表示n个点的有根树个数

根据burnside引理我们有

\[f_n=\frac{\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}{6} \]

那么写成生成函数形式

\[A(x)=\frac{A^3(x)+3A(x^2)A(x)+2A(x^3)}{6}x+1 \]

那么我们现在就可以分治NTT解决这个问题了,但是有一个问题,就是这种自己卷自己的东西,比如\(A^2(x) or A^3(x)\)

我们需要分类讨论处理,因为正常分治时,需要\(A(l,mid)\)卷\(A(0,r-l)\)但是当\(l\)为0的时候\(r-l\)会大于\(mid\),这部分的值还没有求出来。

所以我们分治时考虑左边区间的最大的 \(A(x)\)的贡献,那么当\(l=0\)的时候就可以直接卷积了这部分的值都在\([l,mid]\)里,然后当\(l\ne0\)的时候正常卷积但是需要配一个系数,表示各个\(f\)作为最大值的贡献。

然后求解出\(A(x)\)后,我们可以计算\(C(x)\)表示\(\sum p和\sum q以及\sum s\)了,然后仍然利用burnside引理\(\sum p:C(x)=\frac{A^4(x)+6A^2(x)A(x^2)+3A(x^2)A(x^2)+8A(x)A(x^3)+6A(x^4)}{24}\)

\(\sum q:D(x)=\frac{(A(x)-1)^2+A(x^2)-1}{2}\)

\(\sum s:Q(x)=A(x^2)\)

最后求和就是答案了。

代码细节:

  1. 每一次NTT之前都必须重新处理len求解rev,否则会出大问题,在函数中调用rev后,直接继续使用就出错了
  2. 小心len的长度应该大于等于最后得到的多项式的长度
  3. 必须在A被转化为点值之前使用它计算系数,当它已经被转换后就失效了

代码一定要一气呵成!!!另外不要慌!!!一旦慌了就难以观察出问题的细节,一定要冷静下来,思考方法

#include<bits/stdc++.h>
#define LL long long
#define V inline void
#define I inline int
#define FOR(i,a,b) for(int i=a,end##i=b;i<=end##i;++i)
#define REP(i,a,b) for(int i=a,end##i=b;i>=end##i;--i)
#define go(i,x) for(int i=hed[x];i;i=e[i].pre)
using namespace std;
inline int read()
{
	char x='\0';
	int fh=1,sum=0;
	for(x=getchar();x<'0'||x>'9';x=getchar())if(x=='-')fh=-1;
	for(;x>='0'&&x<='9';x=getchar())sum=sum*10+x-'0';
	return fh*sum;
}
const int N=8e5+9;
const int mod=998244353;
I ksm(int a,int b)
{
	int sum=1;
	while(b)
	{
		if(b&1)sum=1LL*sum*a%mod;
		b>>=1;
		a=1LL*a*a%mod;
	}
	return sum;
}
I inv(int x){return ksm(x,mod-2);}
int jch[N],ijch[N],ni[N];
V init(int mx)
{
	jch[0]=1;
	FOR(i,1,mx)jch[i]=1LL*jch[i-1]*i%mod;
	ijch[mx]=inv(jch[mx]);
	REP(i,mx-1,0)ijch[i]=1LL*ijch[i+1]*(i+1)%mod;
	FOR(i,1,mx)ni[i]=1LL*ijch[i]*jch[i-1]%mod;
}
#define poly vector<int>
int rev[N];
V getrev(int len)
{
	FOR(i,0,len-1)
	{
		rev[i]=(rev[i>>1]>>1)|((i&1)*(len>>1));
	}
}
V NTT(poly &a,int len,int tp)
{
	a.resize(len);
	FOR(i,0,len-1)if(i<rev[i])swap(a[i],a[rev[i]]);
	for(int mid=1;mid<len;mid<<=1)
	{
		int wn=ksm(3,(mod-1)/(mid*2));
		for(int i=0;i<len;i+=mid*2)
		{
			int w=1;
			for(int j=0;j<mid;j++,w=1LL*w*wn%mod)
			{
				int x=a[i+j],y=1LL*a[i+mid+j]*w%mod;
				a[i+j]=(x+y)%mod,a[i+mid+j]=(x-y+mod)%mod;
			}
		}
	}
	if(tp==-1)
	{
		int iv=inv(len);
		FOR(i,0,len-1)a[i]=1LL*a[i]*iv%mod;
		reverse(&a[1],&a[len]);
	}
}
poly operator*(poly a,poly b)
{
	int n=a.size()+b.size()-1,len=1;
	while(len<n)len<<=1;
	getrev(len);
	NTT(a,len,1),NTT(b,len,1);
	FOR(i,0,len-1)a[i]=1LL*a[i]*b[i]%mod;
	NTT(a,len,-1);
	a.resize(n);
	return a;
}
int n;
poly A;
V solve(int lp,int rp)
{
	if(lp==rp)return (A[lp]+=(lp%3==1)*1LL*A[lp/3]*ni[3]%mod)%=mod,void();
	int mid=(lp+rp)>>1;
	solve(lp,mid);
	poly a,b,c;
	a.resize(mid-lp+1),b.resize(rp-lp+1),c.resize(rp-lp+1);
	FOR(i,0,mid-lp)a[i]=A[i+lp];
	FOR(i,0,rp-lp)b[i]=A[i],c[i]=(i%2==0)*A[i/2];
	int n=a.size()+b.size()+c.size()-1,len=1;
	while(len<n)len<<=1;
	getrev(len);
	NTT(a,len,1),NTT(b,len,1),NTT(c,len,1);
	FOR(i,0,len-1)a[i]=(1LL*a[i]*b[i]%mod*b[i]%mod*(lp?ni[2]:ni[6])%mod+1LL*a[i]*c[i]%mod*ni[2]%mod)%mod;
	NTT(a,len,-1);
	FOR(i,mid-lp,rp-lp-1)(A[lp+i+1]+=a[i])%=mod;
	solve(mid+1,rp);
}
int T;
int main()
{
	/*
	poly t(4),g(4);
	FOR(i,0,3)t[i]=g[i]=1;
	int n=t.size()+g.size()-1,len=1;
	while(len<n)len<<=1;
	getrev(len);
	NTT(t,len,1),NTT(g,len,1);
	FOR(i,0,t.size()-1)cout<<t[i]<<endl;//
	FOR(i,0,len-1)t[i]=1LL*t[i]*g[i]%mod;
	NTT(t,len,-1);
	*/
	T=read();
	init(N-9);
	n=1e5;
	
	A.resize(n+1);
	A[0]=1;
	solve(0,n);
	
	int len=1;
	while(len<4*n-1)len<<=1;
	getrev(len);
	A.resize(len);

	poly ans(len),A2(len),A3(len),C(len);
	FOR(i,0,len-1)A2[i]=(i%2==0)*A[i/2],A3[i]=(i%3==0)*A[i/3];
	FOR(i,0,n)ans[i]=(1LL*(i%4==1)*A[i/4]*ni[4]%mod+A2[i])%mod;
	
//	FOR(i,0,len-1)cout<<"A "<<i<<' '<<A[i]<<endl;//
//	FOR(i,0,len-1)cout<<"A2 "<<i<<' '<<A2[i]<<endl;
//	FOR(i,0,len-1)cout<<"A3 "<<i<<' '<<A3[i]<<endl;
	
	NTT(A,len,1),NTT(A2,len,1);
//	FOR(i,0,len-1)cout<<i<<" "<<A[i]<<endl;//
//	FOR(i,0,len-1)A[i]=1LL*A[i]*A2[i]%mod;//
//	NTT(A,len,-1);
//	FOR(i,0,len-1)cout<<A[i]<<endl;
	
	NTT(A3,len,1);
	
//	FOR(i,0,len-1)cout<<"A "<<A[i]<<endl;//
	
	FOR(i,0,len-1)C[i]=(1LL*A[i]*A[i]%mod*A[i]%mod*A[i]%mod*ni[24]%mod
	+1LL*A2[i]*A[i]%mod*A[i]%mod*ni[4]%mod
	+1LL*A2[i]*A2[i]%mod*ni[8]%mod
	+1LL*A3[i]*A[i]%mod*ni[3]%mod)%mod;
	
//	FOR(i,0,len-1)cout<<"vC "<<C[i]<<endl;
	
	NTT(C,len,-1);
	
//	FOR(i,0,len-1)cout<<"C "<<i<<' '<<C[i]<<endl;//
	
//	FOR(i,0,n)cout<<i<<' '<<ans[i]<<endl;//
	FOR(i,1,n)(ans[i]+=C[i-1])%=mod;
	
	FOR(i,0,len-1)C[i]=(1LL*A[i]*A[i]%mod*ni[2]%mod+1LL*A2[i]*ni[2]%mod-A[i]+mod)%mod;
	NTT(C,len,-1);
	FOR(i,0,n)(ans[i]+=mod-C[i])%=mod;
	
	while(T--)
	{
		n=read();
		printf("%d\n",ans[n]);
	}
	return 0;
}

标签:int,Burside,sum,len,转有,lp,根树,无根树,define
来源: https://www.cnblogs.com/dinlon/p/14483814.html