2022 杭电多校 第六场 K - Equipment Upgrade
题意
给定 \(n\), \(m\), 对于一个 \(l\) 长度, 元素都小于 \(m\) 的数组, 可以进行以下两种操作以任意顺序任意次
- 将数组中所有元素模 \(m\) 意义下 \(+1\)
- 将数组循环右移一格
现枚举长度为 \(i\) 的情况下, (\(1 \leq i \leq n\)) 求本质不同数组数, 答案对 \(998244353\) 取模
思路
首先, 对于操作 \(1\) 我们考虑对数组进行差分, 得到一个长度为 \(n\) 且模 \(m\) 意义下和为 \(0\) 的数组 ( 和为 \(km\) ), 即 \(b_i = a_i - a_{i-1}, (i \geq 1)\), \(b_0 = a_0 - a_{n-1}\), 且 \(\sum b_i = 0\), 那么很显然, 我们只需要任取 \(n-1\) 个元素, 最后一个元素补上即可
再考虑操作 \(2\)
由 burnside 引理, 有
其中, \(f(n,i)\) 表示对于 \(n\) 元环, 旋转 \(i\) 次同构的种类数
含义是, 对于一个 \(n\) 圆环, 要计算其旋转 \(i\) 同构的种类数, 则有对于每个 \(j = j + i = j + 2i = \cdots\) 转化为, 每 \(i\) 个元素连一条边, 连边的元素均相等, 最后就会留下 \(\gcd(n,i)\) 个环, 对公式进行化简
观察到, \(g\) 只需要枚举 \(n\) 的因子即可, 而第二个求和公式即是欧拉函数的定义: 小于等于 \(\frac{n}{g}\) 且与其互质的元素个数
现考虑 \(f(n,d)\), 即, \(n\) 元环旋转 \(d\) 次同构, 且有 \(d\) 个环, 每个环大小为 \(\frac{n}{d}\), 求取方案数, 有
注意到, \(\frac{n}{d}\) 可能与 \(m\) 有公因子, 而非公因子部分 \(\frac{n}{gd}\) 必然被 \(k\) 吸收, 记 \(g = \gcd(\frac{n}{d},m)\), 有
那么有前 \(d - 1\) 个元素随便选, 最后一个元素补上, 由于可以选择 \([0, m-1]\) 的整数, 有 \(\frac{m}{m/g} = g\) 个选择, 则有
代码
只展示主要代码, 里面更换了枚举顺序, 先枚举 \(d\), 再枚举 \(n\), 先枚举 \(d\) 的倍数, 等效于枚举 \(n\) 的因子
#include <bits/stdc++.h>
#define de(x) std::cout << #x << " = " << (x) << std::endl
using i64 = long long;
constexpr int P = 998244353;
namespace MF { // math function
constexpr int N = 1 << 17;
int phi[N], phi_pre[N];
int n, prm[N];
bool notP[N] = {false};
void get_phi() {
// get euler function
}
}
int mul(int x,int y) {
return (i64) x * y % P;
}
int add(int x,int y) {
x += y;
if(x >= P) x -= P;
if(x < 0) x += P;
return x;
}
int qpow(int b,int p) {
int res = 1;
for(;p;p>>=1,b=(i64)b*b%P) if(p&1) res = (i64)res * b % P;
return res;
}
void sol() {
int N,m;
std::cin >> N >> m;
std::vector<int> res(N + 1);
for(int d = 1; d <= N; ++d) {
for(int n = d; n <= N; n += d) {
res[n] = add(res[n], mul(qpow(m, d - 1), mul(std::__gcd(m, n / d), MF::phi[n / d])));
}
}
for(int n = 1; n <= N; ++n) {
std::cout << mul(res[n], qpow(n, P - 2)) << " \n"[n == N];
}
}