JZOJ 5988 珂学计树题 【树->括号序列->01序列+Burnside引理】

题面:

JZOJ 5988 珂学计树题 【树->括号序列->01序列+Burnside引理】_第1张图片

题目分析:

朴素的想法是枚举右链的长度,但这样做的复杂度是 O ( n 3 ) O(n^3) O(n3)级别的(考虑卷积是 O ( n 2 l o g n ) O(n^2logn) O(n2logn)的)。
把树转成括号表示:
第一个左括号到第一个右括号之间的部分描述根节点的左子树.剩下的部分描述根节点的右子树.(先序遍历,进左子树打左括号,出左子树打右括号)
转成括号序列后仍然不能直接套用Burnside,因为此时没法保证置换之后也是一个合法括号序列。
考虑将 n n n个左括号, n n n个右括号中非法的括号序列也作为一种方案,这样做是不影响答案的,因为这个非法序列经过循环移位后一定对应一种合法序列。
于是就只需要统计 n n n个左括号, n n n个右括号的本质不同的括号序列数。本质不同是指不能经过循环移位得到。此时就可以套用Burnside引理了,置换群即为 0 0 0 2 n − 1 2n-1 2n1的循环置换。
当置换循环长度为奇数时,循环节是奇数,不能满足 n n n个左括号 n n n个右括号的要求,故只需枚举 i i i为偶数的情况:
A n s = ∑ i = 1 n C 2 g c d ( i , n ) g c d ( i , n ) 2 n = ∑ d ∣ n C 2 d d ∗ φ ( n d ) 2 n Ans={\sum_{i=1}^{n}C_{2gcd(i,n)}^{gcd(i,n)}\over 2n}={\sum_{d|n}C_{2d}^{d}*\varphi(\frac nd)\over 2n} Ans=2ni=1nC2gcd(i,n)gcd(i,n)=2ndnC2ddφ(dn)

线筛出 φ \varphi φ即可做到 O ( n ) O(n) O(n)

burnside引理和polya定理学习

Code:

#include
#define maxn 1000005
const int mod = 998244353;
int n,p[maxn/10],phi[maxn],fac[maxn<<1],inv[maxn<<1],ans;
bool v[maxn];
void Prime(int N){
	phi[1]=1;int m=0;
	for(int i=2;i<=N;i++){
		if(!v[i]) p[++m]=i,phi[i]=i-1;
		for(int j=1,k;j<=m&&(k=p[j]*i)<=N;j++){
			v[k]=1;
			if(i%p[j]==0) {phi[k]=phi[i]*p[j];break;}
			phi[k]=phi[i]*phi[p[j]];
		}
	}
}
void FAC_INV(int N){
	fac[0]=fac[1]=inv[0]=inv[1]=1;
	for(int i=2;i<=N;i++) fac[i]=1ll*fac[i-1]*i%mod,inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
	for(int i=2;i<=N;i++) inv[i]=1ll*inv[i]*inv[i-1]%mod;
}
int C(int n,int m){return 1ll*fac[n]*inv[m]%mod*inv[n-m]%mod;}
inline int ksm(int a,int b){
	int s=1;
	for(;b;b>>=1,a=1ll*a*a%mod) if(b&1) s=1ll*s*a%mod;
	return s;
}
int main()
{
	//freopen("tree.in","r",stdin);
	//freopen("tree.out","w",stdout);
	scanf("%d",&n);
	Prime(n);
	FAC_INV(2*n);
	for(int i=1;i*i<=n;i++) if(n%i==0){
		ans=(ans+1ll*C(2*i,i)*phi[n/i])%mod;
		if(i*i!=n) ans=(ans+1ll*C(2*(n/i),n/i)*phi[i])%mod;
	}
	printf("%d\n",1ll*ans*ksm(2*n,mod-2)%mod);
}

我今天真的是跪了。。 连线性筛都要打错而且还看不出来(phi[i]*phi[p[j]]写成phi[i]*phi[j])。。滚去睡觉。。

你可能感兴趣的:(计数问题)