题解:P4916 [MtOI2018] 魔力环
一种不算简单的暴力推式子做法。
思路
由于求循环同构等价类个数,所以容易想到使用 Burnside 引理进行求解。
Burnside 引理部分
设
假设要求
由于这些环内颜色相同,所以可以缩成一个大点,并且将
缩完之后考虑怎么满足题目的限制,由于每个环其实是由模
设
所以答案是:
后面的式子由前面的式子枚举
\frac{n}{\gcd(n, i)} 得到。
环上计数部分
现在的问题就是如何求出
由于要求的是对连续黑点大小的限制,那我们可以将每一段连续的相同颜色缩起来,这样就是黑白交替的了。
容易发现除了颜色全部相同之外(特判即可),其他的方案都使得缩完后的点数是偶数个。
那么可以去枚举有多少个黑色连通块,此时白色连通块数量与黑色一致,假设是
先不考虑标号的问题,也不考虑谁黑谁白,并给每个联通块标号(其实就是每个连通块是不同的)。
那么白色连通块的总方案数为
此时黑色连通块总方案数就不一样了,由于白色连通块方案数是直接统计
这个上界看着很难受,其实可以直接抬升到
n 的。
先获得一个比较劣的做法,回来考虑点有标号怎么办。
其实也挺好办的,直接绕着环赋值,然后将这个环一直转圈就好了,也就是给答案乘上
其实在枚举了
此时发现当所有的点在转圈统计答案的时候,如果跳出了当前连通块,就等价于将所有的连通块反向转一次,所以这样就导致一个方案被统计了
所以可得:
直接做有点太劣了,考虑优化(以下推式子用到了较多组合数性质,请谨慎观看)。
由于
令
需要注意,该式子在
这样就是
记得特判只有一种颜色的情况。
代码
#include <iostream>
#include <vector>
#include <cstring>
#include <bitset>
#include <algorithm>
#include <map>
#include <iomanip>
#include <cassert>
#include <unordered_map>
#include <set>
#include <random>
#include <queue>
using namespace std;
const int N = 2e5 + 10, mod = 998244353;
#define int long long
#define fi first
#define se second
#define emp emplace_back
using pii = pair <int, int>;
using ld = long double;
using ll = long long;
const int inf = 0x3f3f3f3f;
const ld eps = 1e-9;
int fac[N], inv[N];
int C(int n, int m) {if (n < 0 || m < 0) return 0; return n >= m ? fac[n] * inv[m] % mod * inv[n - m] % mod : 0;}
int qpow(int x, int b)
{
int res = 1;
while (b)
{
if (b & 1) res = res * x % mod;
x = x * x % mod;
b >>= 1;
}
return res;
}
int calc(int n, int m, int k)
{
int flag = 1, ans = 0;
for (int j = 1; j <= n / 2; j++)
{
flag = 1;
for (int i = 0; i <= 0; i++)
{
(ans += flag * C(m - i * k - 1, j - 1) % mod * C(j, i) % mod * C(n - m - 1, j - 1) % mod * qpow(j, mod - 2) % mod) %= mod;
flag = mod - flag;
}
}
flag = mod - 1;
for (int i = 1; i <= n; i++)
{
int T = m - i * k - 1;
if (T < 0) continue;
(ans += flag * qpow(i, mod - 2) % mod * C(T, i - 1) % mod * C(T - i + 1 + n - m - 1, n - m - i) % mod) %= mod;
flag = mod - flag;
}
return n * ans % mod;
}
int phi(int x)
{
int ans = x;
for (int i = 2; i * i <= x; i++)
{
if (x % i) continue;
ans = ans / i * (i - 1);
while (x % i == 0) x /= i;
}
if (x != 1) ans = ans / x * (x - 1);
return ans;
}
int n, m, k;
signed main()
{
ios :: sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> n >> m >> k;
if (m == 0)
{
cout << 1 << '\n';
return 0;
}
if (n == m)
{
cout << (n <= k) << '\n';
return 0;
}
fac[0] = inv[0] = 1;
for (int i = 1; i < N; i++) fac[i] = fac[i - 1] * i % mod;
inv[N - 1] = qpow(fac[N - 1], mod - 2);
for (int i = N - 2; i >= 1; i--) inv[i] = inv[i + 1] * (i + 1) % mod;
int ans = 0;
for (int i = 1; i * i <= n; i++)
{
if (n % i) continue;
if (m % i == 0) ans += calc(n / i, m / i, k) * phi(i) % mod;
if (i * i != n && m % (n / i) == 0) ans += calc(i, m / (n / i), k) * phi(n / i) % mod;
}
cout << ans % mod * qpow(n, mod - 2) % mod << '\n';
return 0;
}