【luogu P4916 魔力環】【Burnside引理+組合數學】

題意

給出n,m,kn,m,k,問有多少個長度爲nn的環,環上恰有mm個黑珠子和nmn-m個白珠子,且不存在一段連續的黑珠子的長度超過kk。兩個環相等當且僅當他們循環同構。
n,m,k105n,m,k\le10^5

分析

首先根據Burnside引理,設f(d)f(d)表示週期爲dd的合法序列數量,則ans=1ni=0n1f(gcd(n,i))=dnf(d)φ(nd)nans=\frac{1}{n}\sum_{i=0}^{n-1}f(gcd(n,i))=\frac{\sum_{d|n}f(d)\varphi(\frac{n}{d})}{n}
顯然dd必然也要是gcd(n,m)gcd(n,m)的約數,考慮如何計算f(d)f(d)
問題變成要將mm個黑珠子插入到nn個白珠子組成的縫隙當中,滿足每個縫隙放不超過kk個黑珠子,求有多少個循環同構的方案。
考慮枚舉第一個白珠子和最後一個白珠子之間的黑珠子方案,則ans=i=0k(i+1)g(n1,mi,k)ans=\sum_{i=0}^k(i+1)g(n-1,m-i,k)
其中g(n,m,k)g(n,m,k)表示將mm個相同的球放入nn個不同的盒子中,且每個盒子中的球不能找過kk個的方案,計算g(n,m,k)g(n,m,k)顯然可以用容斥在O(mk)O(\frac{m}{k})的複雜度內求出。
所以複雜度爲O(mkk)=O(m)O(\frac{m}{k}*k)=O(m)
於是總的複雜度爲O(n+σ(gcd(n,m)))O(n+\sigma(gcd(n,m)))

代碼

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>

typedef long long LL;

const int N=100005;
const int MOD=998244353;

int n,m,k,jc[N*2],ny[N*2];

int ksm(int x,int y)
{
	int ans=1;
	while (y)
	{
		if (y&1) ans=(LL)ans*x%MOD;
		x=(LL)x*x%MOD;y>>=1;
	}
	return ans;
}

int gcd(int x,int y)
{
	if (!y) return x;
	else return gcd(y,x%y);
}

int get_phi(int n)
{
	int ans=n;
	for (int i=2;i*i<=n;i++)
		if (n%i==0)
		{
			ans=ans/i*(i-1);
			while (n%i==0) n/=i;
		}
	if (n>1) ans=ans/n*(n-1);
	return ans;
}

int C(int n,int m)
{
	return (LL)jc[n]*ny[m]%MOD*ny[n-m]%MOD;
}

int calc(int n,int m)
{
	if (!n) return !m?1:0;
	int ans=0;
	for (int i=0;i<=n&&(k+1)*i<=m;i++)
		if (i&1) (ans+=MOD-(LL)C(n,i)*C(m-(k+1)*i+n-1,n-1)%MOD)%=MOD;
		else (ans+=(LL)C(n,i)*C(m-(k+1)*i+n-1,n-1)%MOD)%=MOD;
	return ans;
}

int solve(int n,int m)
{
	int ans=0;
	for (int i=0;i<=std::min(m,k);i++) (ans+=(LL)(i+1)*calc(n-1,m-i)%MOD)%=MOD;
	return ans;
}

int main()
{
	scanf("%d%d%d",&n,&m,&k);
	jc[0]=jc[1]=ny[0]=ny[1]=1;
	for (int i=2;i<=n+m;i++) jc[i]=(LL)jc[i-1]*i%MOD,ny[i]=(LL)(MOD-MOD/i)*ny[MOD%i]%MOD;
	for (int i=2;i<=n+m;i++) ny[i]=(LL)ny[i-1]*ny[i]%MOD;
	if (n==m) {printf("%d\n",k>=n?1:0);return 0;}
	int ans=0,d=gcd(n,m);
	for (int i=1;i*i<=d;i++)
		if (d%i==0)
		{
			(ans+=(LL)solve((n-m)/i,m/i)*get_phi(i)%MOD)%=MOD;
			if (d/i!=i) (ans+=(LL)solve((n-m)/(d/i),m/(d/i))*get_phi(d/i)%MOD)%=MOD;
		}
	printf("%d\n",(LL)ans*ksm(n,MOD-2)%MOD);
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章