T1. 整除
创建一个由数对组成的数组 \(C\),其中每个元素为 \((i, a_i) \ (1 \leqslant i \leqslant n)\)。令 \(C_{x, 1}\) 和 \(C_{x_, 2}\) 分别表示数对 \(C_x\) 的第一个(即 \(i\))和第二个元素(即 \(a_i\))。
若两数对 \(C_i, C_j\) 满足 \(C_{i,1} \cdot C_{j,1} \Big| C_{i,2} \cdot C_{j,2}\),则称这对数对为特殊数对。
接下来我们要计算所有这样的无序对 \((C_i, C_j)\) 的总数。
对于所有数对 \(C_i\),计算 \(C_{i,1}\) 和 \(C_{i,2}\) 的最大公约数 \(\gcd(C_{i,1}, C_{i,2})\),将每个数对的两个元素同时除以这个最大公约数。记处理后的新数对数组为 \(D\) 。
命题:
\((D_i,D_j)\) 是特殊对,当且仅当 \((C_i,C_j)\) 是特殊对。
证明:
为清晰起见,令 \(C_i = (a, b)\),\(C_j = (c, d)\);设 \(g_1 = \gcd(a, b)\),\(g_2 = \gcd(c, d)\) 。最后定义 \(D_i = (a', b')\) 和 \(D_j = (c', d')\),其中 \(a = g_1 \cdot a'\),\(b = g_1 \cdot b'\),\(c = g_2 \cdot c'\),以及 \(d = g_2 \cdot d'\) 。
然后, \((C_i, C_j)\) 是特殊对 \(\Rightarrow \ a \cdot c \Big| b \cdot d\) \(\Rightarrow \, (g_1 \cdot a') \cdot (g_2 \cdot c') \Big| (g_1 \cdot b') \cdot (g_2 \cdot d')\) \(\Rightarrow \, a' \cdot c' \Big| b' \cdot d'\) \(\Rightarrow\) \((D_i, D_j)\) 也是特殊对。
因此,原问题等价于求 \(D\) 中特殊无序数对的数量。
命题:
\((D_i, D_j)\) 是特殊对,当且仅当 \(D_{i,1} \Big| D_{j,2}\) 且 \(D_{j,1} \Big| D_{i,2}\) 。
(证明是显然的,作为练习留给读者;只需利用 \(D_{i,1}\) 和 \(D_{i,2}\) 互质的事实即可。)
所以,如果 \((D_i, D_j)\) 是特殊对,则有
其中 \(D'_{i,2}\) 和 \(D'_{j,2}\) 分别是 \(D_{i,2}\) 和 \(D_{j,2}\) 的因子。
记 \(dp_i[x][y]\) 表示使得 \(x = D_{j,1}\) 且 \(y \Big| D_{j,2}\) 的 \(j(j < i)\) 的数量
利用上述推导,不难看出使得 \((D_i, D_j)\) 为特殊对的 \(j(j < i)\) 的个数等于
其中 \(F\) 是 \(D_{i,2}\) 的所有因子的集合
将所有 \(i\) 对应的该值求和,即可得到答案。
代码实现
#pragma GCC optimize ("O2")
#pragma GCC optimize ("unroll-loops")
#pragma GCC target ("avx2")
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
vector<int> factor(int n) {
vector<int> res;
for (int i = 1; i*i <= n; ++i) {
if (n%i) continue;
res.push_back(i);
if (i*i != n) res.push_back(n/i);
}
return res;
}
void solve() {
int n;
cin >> n;
vector<pair<int, int>> ps;
for (int i = 1; i <= n; ++i) {
int x;
cin >> x;
int g = gcd(i, x);
ps.emplace_back(i/g, x/g);
}
ll ans = 0;
unordered_map<int, unordered_map<int, int>> dp;
for (auto [a, b] : ps) {
auto ds = factor(b);
for (int d : ds) {
ans += dp[a][d];
}
for (int d : ds) {
dp[d][a]++;
}
}
cout << ans << '\n';
}
int main() {
cin.tie(nullptr) -> sync_with_stdio(false);
int t;
cin >> t;
while (t--) solve();
return 0;
}
T2. MEX
考虑从小到大往序列中填数。
初始时,我们假设每个元素的值均为 \(-1\),然后按 \(0\) 到 \(n-1\) 的顺序填数,并分析这样填数时每个数对答案的贡献。为了方便,我们设 \(i\) 在序列中的下标是 \(p_i\) 。
假设当前填的数是 \(x\) 。设 \(p=\max(p_0, p_1, \cdots, p_x)\),那么对于 \(i \geqslant p\) 的所有下标 \(i\),\(\mathrm{mex}(a_1, a_2, \cdots , a_i)\) 的值都会从 \(x\) 变成 \(x+1\) ,所以这个数对答案的贡献是 \(n-p+1\) 。
这使得我们可以直接设 \(f(x, p, k)\) 为,已经在序列中填了 \(0 \sim x\) 的数, \(\max(p_0, p_1, \cdots, p_x) = p\) ,当前的答案为 \(k\) 的方案数。
我们考虑哪些状态可以转移到 \(f(x, p, k)\) 。存在两种情况:
- \(p\) 被更新了,即 \(x\) 在 \(0, 1, \cdots, x-1\) 里所有数的右边。贡献为 \(\sum\limits_{i = 1}^{p-1} f(x-1, i, k-(n-p+1))\) 。
- \(p\) 没有被更新,即存在 \(0, 1, \cdots, x-1\) 的数在 \(x\) 的右边。这样的话 \(x\) 填的位置有 \(p-x\) 种选择,那么贡献为 \((p-x) \cdot f(x-1, p, k-(n-p+1))\) 。
总复杂度为 \(O(n^2(k-n))\) 。
T3. 三人竞赛
不妨只考虑Alice的答案。
设 \(x_i = a_i-b_i\),\(y_i = a_i-c_i\),那么对于区间 \([l, r]\),原条件可以写成 \(\sum\limits_{i=l}^r x_i \geqslant 0\) 且 \(\sum\limits_{i=l}^r x_i \geqslant 0\) 。
然后我们设 \(X_i = \sum\limits_{j=1}^i x_j\),\(Y_i = \sum\limits_{j=1}^i y_j\),那么条件可以进一步改写为 \(X_r \geqslant X_{l-1}\) 且 \(Y_r \geqslant Y_{l-1}\) 。
然后其实就变成了,查询有多少对 \((p, q)\) 使得 \(p < q\) 且 \(X_p \leqslant X_q\) 且 \(Y_p \leqslant Y_q\)。满足这个条件的每一对 \((p, q)\) 都对应一个合法的区间 \([p+1, q]\) 。
于是这样就转化为了一道经典的三维偏序问题。
时间复杂度为 \(O(n\log n)\) 。