loj6519 魔力环

解题思路

考虑顺时针旋转 \(i\) 步得到的结果,根据Burnside引理,有

\[Ans=\frac{\sum\limits_{i=0}^{n-1}C(i)}{n} \]

\(C(i)\) 为旋转 \(i\) 步时不动点的数量。

实际上,旋转 \(i\) 步得到的是 \(\frac{n}{\gcd(n,i)}\) 个互不相干的环,每个环都是一个置换。不难发现,若一个方案在旋转 \(i\) 步的情况下为不动点,那么对于分割出来的每个小环,要么都为黑色,要么都为白色。

可以得到,对于 \(\gcd(n,i)\) 相等的 \(i\) ,它们对答案的贡献是一样的。

所以可以枚举每个小环的长度 \(l\) 。不难发现 \(l\) 需要是 \(n\) 的因数。而由于每个小环要么全黑,要么全白,所以黑点数量应当为 \(l\) 的整数倍。即 \(l|\gcd(n,m)\)

所以答案可以写成这样:

\[Ans = \frac{\sum\limits_{l|\gcd(n,m)}f(l)\phi(\frac{n}{l})}{n} \]

用到了使用Burnside的时候常用的东西:

\[\sum\limits_{i=0}^{n-1}\gcd(i,n)=\sum\limits_{d|n}\sum\limits_{i=1}^{\frac{n}{d}}[(i,\frac{n}{d})=1]=\sum\limits_{d|n}\phi(\frac{n}{d}) \]

接下来就要解决如何求 \(f(l)\) 。可以这样考虑:

对于环长 \(l\)\(n\) 个点被分成了 \(\frac{n}{l}\) 个环。不妨将每个点以其所在的点标号,那么就会产生类似于 \(1,2,3,1,2,3\) 这样的重复。也就是说,还可以将“ \(1,2,3\) ”看成 \(1\) 个环,总共 \(l\) 个环,每个环 \(\frac{n}{l}\) 个点。为了方便叙述,不妨将其称为“第二种环”。

每个“第二种环”恰好有“第一种环”上每个环里一个点,也就是说,恰好有 \(\frac{m}{l}\) 个黑点。即需要在一个 \(\frac{n}{l}\) 元环上选 \(\frac{m}{l}\) 个黑点,其中连续的黑点不超过 \(k\) 个。注意这里的环相当于有了标号,旋转相同的不算重复。

可以考虑断环成链,然后枚举链首尾连续的黑点个数之和 \(j\) 。如果记 \(x\) 个黑点分成 \(y\) 份,每份可以为空的方案数为 \(F(x,y)\) ,那么环上的答案就是:

\[f(l)=\sum\limits_{j=0}^k(j+1)F(\frac{m}{l}-j,\frac{n}{l} - \frac{m}{l} - 1) \]

\(F\) 的值可以通过容斥得到

\[F(x,y)=\sum\limits_{i=0}^{i * (k+1) \leqslant x, i \leqslant y}(-1)^i{y\choose i}{x - i * (k + 1) + y - 1\choose y - 1} \]

计算 \(F(x,y)\) 的时间复杂度是 \(O(\frac{x}{k})\) 的,计算 \(f(l)\) 的时间复杂度是 \(O(\frac{n}{l})\) 的。所以总的时间复杂度是 \(O(n)\) 的。

参考程序

#include <cstdio>

#define Maxn 100010
#define Mod 998244353

int Fact[Maxn], Inv[Maxn], Phi[Maxn], Vis[Maxn], Prime[Maxn], n, m, k;

inline void Pre();
inline int f(int x);
inline int F(int x, int y);

#define Add(x,y) ((x)+(y))%Mod
#define Dec(x,y) (x)>(y)?(x)-(y):(x)-(y)+Mod
#define Mul(x,y) 1ll*(x)*(y)%Mod
inline int Pow(int x,int y){int A=1;for(;y;y>>=1,x=Mul(x,x))if(y&1)A=Mul(A,x);return A;}
#define C(x,y) 1LL*Fact[x]*Inv[y]%Mod*Inv[(x)-(y)]%Mod

int main() {
	scanf("%d%d%d", &n, &m, &k);
	if (k == 0) return printf("%d\n", m ? 0 : 1), 0;
	if (m == 0) return printf("1\n"), 0;
	Pre();
	int gcd = [](int x, int y)->int{int T = x % y; while(T) x = y, y = T,T = x % y; return y; }(n, m);
	int Ans = 0;
	for (int i = 1; i <= gcd; ++i) 
		if (!(gcd % i)) 
			Ans = Add(Ans, Mul(f(i), Phi[i])); 
	Ans = Mul(Ans, Pow(n, Mod - 2));
	printf("%d\n", Ans);
	return 0;
}

inline void Pre() {
	Fact[0] = 1; for (int i = 1; i <= n; ++i) Fact[i] = Mul(Fact[i - 1], i);
	Inv[n] = Pow(Fact[n], Mod - 2); for (int i = n - 1; i >= 0; --i) Inv[i] = Mul(Inv[i + 1], i + 1);
	Phi[1] = 1;
	for (int i = 2; i <= n; ++i) {
		if (!Vis[i]) Phi[i] = i - 1, Prime[++Prime[0]] = i;
		for (int j = 1; j <= Prime[0] && 1LL * i * Prime[j] <= n; ++j) {
			Vis[i * Prime[j]] = 1;
			if (i % Prime[j] == 0) { Phi[i * Prime[j]] = Phi[i] * Prime[j]; break; }
			Phi[i * Prime[j]] = Phi[i] * (Prime[j] - 1);
		}
	}
	return;
}

inline int f(int l) {
	if (n / l - m / l == 1) return (m / l <= k) ? m / l + 1 : 0;
	int Ans = 0;
	for (int j = 0; j <= k; ++j)
		Ans = Add(Ans, Mul(j + 1, F(m / l - j, n / l - m / l - 1)));
	return Ans;
}

inline int F(int x, int y) {
	int Ans = 0;
	for (int i = 0; i * (k + 1) <= x && i <= y; ++i) {
		if (i & 1) Ans = Dec(Ans, Mul(C(y, i), C(x - i * (k + 1) + y - 1, y - 1)));
		else Ans = Add(Ans, Mul(C(y, i), C(x - i * (k + 1) + y - 1, y - 1)));
	}
	return Ans;
}
posted @ 2019-12-09 16:34  chy_2003  阅读(289)  评论(0编辑  收藏  举报