2022CCPC Online Contest J - Count Permutation
英语不好吃大亏,题意想表达的是对于所有 \(n\) 元排列的 \(w(P)\) 最小,并不是满足 \(p_1 = s, \, p_m = t\) 的排列,因此最小的 \(w(P) = 2\),如果你看假了,这个题分讨没法做。
首先 \(w(P) = 2\) 能在什么条件满足?\(P\) 如果是个模 \(n\) 等差数列那么,我们容易发现 \(S = \{p, \, -q\}\) 满足 \(p + q = n\),而显然公差 \(d\) 满足 \(\gcd(d, n) = 1\),否则生成的模 \(n\) 序列是原剩余系的子群。
而设 \(a_i = id\),那么 \(a_1 = d \equiv s \pmod n, \, a_m = md \equiv t \pmod n\),作差可得
那么不妨写出公式
来一发莫比乌斯反演
为了得到
的值,我们不妨考虑这样一个同余方程,在 \([1, \, N]\) 范围内,\(p\) 有多少解?
如果设 \(G = \gcd(n, m)\),那么当 \(G \nmid t\) 时,一定无解,否则一定有解。
考虑有解的情况,设 \(n' = \frac{n}{G}, \, m' = \frac{m}{G}, \, t' = \frac{t}{G}\),此时 \(\gcd(n', \, m') = 1\),利用扩欧可以求得一个逆元 \(m'^{-1}\) 和一个特解 \(p_0\),进而得到
由于所有的解满足 \(p_0 + Tn'(T \in \mathbb{N})\) 的形式,根据 \(1 \le p \le N\) 不难得到,\(1 \le p_0 + Tn' \le N\) 。
进而有 \(\frac{1 - p_0}{n'} \le T \le \frac{N - p_0}{n'}\) 。
于是答案可以写作
为了防止因为“特性”导致除法按照绝对值下取整,我们给分子同时加一个 \(n'\) 就好。
返回原题,把符合条件的系数带入即可。
对 \(n\) 进行 \(O\left(\sqrt{\frac{n}{\ln{n}}}\right)\) 的质因数分解得,可以计算每个因数的莫比乌斯函数,右边的式子需要解一个同余方程,复杂度 \(O(\log{n})\),上限 \(O\left(d(n)\log{n}\right)\),由于 \(\mu(k)\) 函数的特殊性,有很大一部分都是 \(0\),不用考虑,显然跑不满,每个因数只出现 \(0\) 或 \(1\) 次才是有意义的,两者复杂度上限是负相关的,因此可以通过。
时间复杂度 \(O\left(\sqrt{\frac{n}{\ln{n}}} + d(n)\log{n}\right)\)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e6 + 10;
ll n, m, s, t, ans, res;
ll primes[N], cnt, factor[N], sz;
bool st[N];
void init() {
for (int i = 2; i <= N; i ++ ) {
if (!st[N]) primes[cnt ++ ] = i;
for (int j = 0; i * primes[j] < N; j ++ ) {
st[i * primes[j]] = 1;
if (i % primes[j] == 0) break;
}
}
}
ll exgcd(ll a, ll b, ll &x, ll &y) {
if (!b) return x = 1, y = 0, a;
ll d = exgcd(b, a % b, y, x);
y -= a / b * x;
return d;
}
void dfs(ll sum, ll mu, ll p) {
if (p == sz) {
// for (int i = 1; i <= (n - 1) / sum; i ++ ) {
// if (((m - 1) * sum * i - (t - s)) % n == 0) {
// res += mu;
// }
// }
ll G = __gcd(n, (m - 1) * sum);
if (abs(s - t) % G != 0) return;
ll coff = (s - t) / G, _m = (m - 1) * sum / G, _n = n / G;
ll a, b, sol;
exgcd(_m, _n, a, b);
sol = coff * a % _n;
ans += mu * (((n - 1) / sum - sol) / _n - (_n - sol) / _n + 1);
// cout << sum << " " << mu << " " << ans << " " << res << "\n";
return;
}
dfs(sum, mu, p + 1);
dfs(sum * factor[p], -mu, p + 1);
}
int main() {
init();
cin >> n >> m >> s >> t;
ll x = n;
for (int i = 0; i < cnt && x >= primes[i]; i ++ ) {
if (x % primes[i] == 0) {
factor[sz ++ ] = primes[i];
while (x % primes[i] == 0) x /= primes[i];
}
}
if (x > 1) factor[sz ++ ] = x;
dfs(1, 1, 0);
cout << ans << "\n";
return 0;
}