LGP6276题解

众所周知,排列是一个置换,一个置换是一车环。

步数就是这些环长的 \(lcm\)

如果你去思考直接 DP,会发现很困难,根本设不出来状态。于是考虑正难则反:每个质数幂 \(p^k\) 对答案的贡献。

可以发现,如果设 \(f[p^k]\)\(p^k\)\(f[p^k]\) 个排列的 \(lcm\) 中出现过,那么答案是 \(\sum_{p^k\leq n}p^{f[p^k]}\)

考虑去计算这个 \(f\)。计算出现过似乎也很困难,正难则反计算没出现过的。(后文令 \(t=p^k\)

然后有一个显然的 DP:设 \(dp[n]\)\(n\) 个元素构成的置换群中,没有一个环的长度是 \(t\) 的倍数。那么似乎只需要从非 \(t\) 的倍数转移过来就可以了。

但是我们并不会计算新加入若干个元素会产生多少个新排列。考虑原本有 \(n\) 个元素的情况下加入了一个大小为 \(m\) 的环。

我们强制钦定 \(1\) 必须在这个环中,那么我们还需要从 \([2,n+m]\) 中选出 \([m-1]\) 个数,然后有 \((m-1)!\) 种不同的排列方式。所以方案数乘上了 \(\binom{n+m-1}{m-1}(m-1)!=\frac{(n+m-1)!}{n!}\)

然后我们好像会了 \(O(n^3)\) 计算这个玩意儿(

考虑优化,正难则反。我们不计算非 \(t\) 倍数位置转移过来的值,考虑维护前面所有位置转移过来的值减去 \(t\) 倍数转移过来的值。这个是很好维护的。

于是做到了 \(O(n^2)\)

#include<cstdio>
typedef unsigned ui;
typedef __uint128_t LL;
typedef unsigned long long ull;
const ui M=7505;
struct Barrett{
	ull m,b;
	Barrett(const ui&m=1):m(m),b(((LL(1)<<80)+m-1)/m){}
	friend inline ull operator%(const ull&a,const Barrett&mod){
		return a-mod.m*(LL(mod.b)*a>>80);
	}
}mod,MOD;
ui n,m,tm,TM,c[M],S[M],dp[M],minp[M];
inline ui pow(ui a,ui b){
	ui ans(1);for(;b;b>>=1,a=1ull*a*a%mod)if(b&1)ans=1ull*ans*a%mod;return ans;
}
signed main(){
	ui ans(1),fac(1);
	scanf("%u%u",&n,&m);minp[1]=1;
	mod=Barrett(tm=m);MOD=Barrett(TM=m-1);
	for(ui i=1;i<=n;++i)c[i]=1,fac=1ull*fac*i%MOD;
	for(ui i=2;i<=n;++i)if(!minp[i]){
		for(ui j=i*i;j<=n;j+=i)minp[j]=1;
		for(ui j=i;j<=n;j*=i)minp[j]=i;
	}
	for(ui i=2;i<=n;++i){
		for(ui j=1;j+i-1<=n;++j)c[j]=1ull*c[j]*(i+j-2)%MOD;
		if(minp[i]<=1)continue;
		const ui&pk=i;ui sum(1);
		dp[0]=1;S[0]=1;
		for(ui i=1;i<=n;++i){
			if(pk<=i)S[i]=1ull*S[i-pk]*c[i-pk+1]%MOD;else S[i]=0;
			dp[i]=(TM+sum-S[i])%MOD;
			S[i]=(1ull*S[i]*i+dp[i])%MOD;
			sum=(1ull*sum*i+dp[i])%MOD;
		}
		ans=1ull*ans*pow(minp[i],TM+fac-dp[n])%mod;
	}
	printf("%u",ans);
}

但是这样真的够快吗?

考虑生成函数。

置换是一个典型的有标号计数问题。只需要计算出一个环的 EGF 再将其 \(\exp\) 即可。

而一个环的 EGF 显然是 \(\sum_{i=1}\frac{(i-1)!}{i!}x^i-\sum_{i=1}\frac{(it-1)!}{(it)!}x^{it}\)

\[\exp(\sum_{i=1}\frac{(i-1)!}{i!}x^i-\sum_{i=1}\frac{(it-1)!}{(it)!}x^{it}) \]

\[\exp(\sum_{i=1}\frac{1}{i}x^i-\sum_{i=1}\frac{1}{it}x^{it}) \]

\[\exp(-\ln(1-x)+\frac{1}{t}\ln(1-x^{t})) \]

\[\frac{(1-x^t)^{\frac{1}{t}}}{1-x} \]

注意到 \(\frac{1}{1-x}\) 是前缀和,分子又只在 \(t\) 的倍数处有值,所以可以考虑改写一下这个东西:

\[[\frac{x^n}{n!}]\frac{(1-x^{t})^{\frac{1}{t}}}{1-x} \]

\[[\frac{x^n}{n!}]\frac{(1-x^{t})^{\frac{1}{t}}}{1-x^{t}} \]

\[n![x^{\lfloor\frac{n}{t}\rfloor}](1-x)^{\frac{1}{t}-1} \]

剩下的就交给广义二项式定理吧。

\(m=\lfloor\frac{n}{t}\rfloor\)

\[n!\binom{\frac{1}{t}-1}{m!}(-1)^{m} \]

\[n!\prod_{i=1}^{m}-\frac{\frac{1}{t}-1-i+1}{i} \]

\[n!\prod_{i=1}^{m}\frac{it-1}{it} \]

但是这个是在指数上做的,所以模数并不是质数。

注意到这个 \(it\)\(n!\) 分成了 \(m+1\) 段,可以将 \(n!\) 看做一个序列,我们只需要支持查询区间乘积即可。

使用猫树可以做到 \(O(n\log n)\) 的复杂度。

#include<cstdio>
typedef unsigned ui;
typedef __uint128_t LL;
typedef unsigned long long ull;
const ui M=7505;
ui n,m,minp[M];
struct Barrett{
	ull m,b;
	Barrett(const ui&m=1):m(m),b(((LL(1)<<80)+m-1)/m){}
	friend inline ull operator%(const ull&a,const Barrett&mod){
		return a-mod.m*(LL(mod.b)*a>>80);
	}
}mod,MOD;
struct DS{
	ui D,len,lg[8192],prod[15][8192];
	inline void init(const ui&n){
		D=0;len=1;
		while((1<<D)<=n)++D,len<<=1;
		for(ui i=1;i<=len;++i)prod[0][i]=i;
		for(ui i=1;i<=len;++i)lg[i]=lg[i>>1]+1;
		for(ui d=1;d<=D;++d){
			for(ui k=1;k<=(len>>d);++k){
				const ui&L=k-1<<d,&R=k<<d,&mid=L+R>>1;
				prod[d][mid]=mid;prod[d][mid+1]=mid+1;
				for(ui i=mid-1;i>=L+1;--i)prod[d][i]=1ull*prod[d][i+1]*i%MOD;
				for(ui i=mid+1+1;i<=R;++i)prod[d][i]=1ull*prod[d][i-1]*i%MOD;
			}
		}
	}
	inline ui operator()(const ui&L,const ui&R){
		if(L>R)return 1;if(L==R)return L;
		ui d=lg[L-1^R-1];return 1ull*prod[d][L]*prod[d][R]%MOD;
	}
}SGT;
inline ui pow(ui a,ui b){
	ui ans(1);for(;b;b>>=1,a=1ull*a*a%mod)if(b&1)ans=1ull*ans*a%mod;return ans;
}
inline ui Solve(const ui&k){
	const ui&m=n/k;ui ans;ans=SGT(m*k+1,n);
	for(ui i=1;i<=m;++i)ans=1ull*ans*SGT((i-1)*k+1,i*k-1)%MOD,ans=1ull*ans*(i*k-1)%MOD;
	return ans;
}
signed main(){
	ui ans(1),fac(1);
	scanf("%u%u",&n,&m);
	mod=Barrett(m);MOD=Barrett(m-1);SGT.init(n);
	for(ui i=1;i<=n;++i)fac=1ull*fac*i%MOD;
	for(ui i=2;i<=n;++i){
		if(!minp[i]){
			for(ui j=i*i;j<=n;j+=i)minp[j]=1;
			for(ui j=i;j<=n;j*=i)minp[j]=i;
		}
		if(minp[i]>1)ans=1ull*ans*pow(minp[i],m-1+fac-Solve(i))%mod;
	}
	printf("%u",ans);
}
posted @ 2022-03-14 20:17  Prean  阅读(27)  评论(0编辑  收藏  举报
var canShowAdsense=function(){return !!0};