Serval and Modulo 题解:模运算 + 因数分解
题目链接
题目分析
Serval 给定了两个数组 $ a $ 和 $ b $,其中 $ b $ 是通过将 $ a $ 中的每个元素对某个魔法数 $ k $ 取模后得到的,并且 $ b $ 被打乱了。我们需要找到一个可能的魔法数 $ k $(满足 $ 1 \leq k \leq 10^9 $),使得 $ b $ 可以由 $ a $ 按照上述规则生成。如果不存在这样的 $ k $,输出 $ -1 $。
思路大意
这道题的核心目标是找到一个合适的 $ k $,使得:
- 对于数组 $ a $ 的每个元素 $ a_i $,有 $ a_i \mod k = b_j $,其中 $ b_j $ 是数组 $ b $ 中的一个元素。
- 数组 $ b $ 是通过重新排列 $ [a_1 \mod k, a_2 \mod k, \dots, a_n \mod k] $ 得到的。
核心观察
- 如果数组 $ a $ 和 $ b $ 完全相同(即排序后相等),那么任意一个大于等于最大值的 $ k $ 都是合法答案。
- 如果数组 $ a $ 和 $ b $ 不同,则需要通过模运算找到一个合适的 $ k $。
- 关键在于利用 $ \text{sum}(a) - \text{sum}(b) $ 的差值 $ \text{diff} $ 来约束 $ k $ 的可能取值。
数学推导
假设 $ k $ 是一个合法的答案,则对于所有 $ i $,有:
\[a_i \mod k = b_j
\]
这意味着 $ a_i - b_j $ 必须是 $ k $ 的倍数。因此,我们可以得出:
\[k \mid (a_i - b_j)
\]
进一步地,如果我们对所有 $ i $ 计算 $ a_i - b_j $ 的和 $ \text{diff} = \text{sum}(a) - \text{sum}(b) $,则 $ k $ 必须是 $ \text{diff} $ 的因数。
算法设计
基于上述分析,我们可以设计如下算法:
- 输入与初始化:读入数组 $ a $ 和 $ b $,并计算两者的差值 $ \text{diff} = \text{sum}(a) - \text{sum}(b) $。
- 排序比较:对数组 $ a $ 和 $ b $ 排序。如果排序后的 $ a $ 和 $ b $ 相同,则直接输出一个较大的 $ k $(如 $ 10^6 + 1 $)。
- 检查差值:如果 $ \text{diff} \leq 0 $,则输出 $ -1 $。
- 枚举因数:枚举 $ \text{diff} $ 的所有因数 $ k $,并检查是否满足条件:
- 将数组 $ a $ 中的每个元素对 $ k $ 取模,得到新数组 $ c $。
- 排序 $ c $ 并与 $ b $ 比较,如果相等,则 $ k $ 是合法答案。
- 返回结果:如果找到合法的 $ k $,输出它;否则输出 $ -1 $。
代码详解
以下是完整的代码实现及其详细解释:
#include <bits/stdc++.h>
#define int long long
#define all(x) x.begin(),x.end()
#define rall(x) x.rbegin(),x.rend()
#define pb push_back
#define pii pair<int,int>
using namespace std;
const int mod = 998244353;// 计算 gcd 的函数
int gcd(int a, int b) { return b ? gcd(b, a % b) : a; }// 快速幂函数
int qpw(int a, int b) {int ans = 1;while (b) {if (b & 1) ans = ans * a % mod;a = a * a % mod;b >>= 1;}return ans;
}// 求逆元函数
int inv(int x) { return qpw(x, mod - 2); }void solve() {int n; cin >> n; // 输入数组长度vector<int> a(n), b(n);int dif = 0;// 输入数组 a 并计算 sum(a)for (int i = 0; i < n; i++) {cin >> a[i];dif += a[i];}// 输入数组 b 并计算 sum(b)for (int i = 0; i < n; i++) {cin >> b[i];dif -= b[i];}// 对数组 a 和 b 排序sort(all(a));sort(all(b));// 如果 a 和 b 相同,直接输出一个较大的 kif (a == b) {cout << (int)(1e6 + 1) << '\n';return;}// 如果 diff <= 0,说明找不到合法的 kif (dif <= 0) {cout << -1 << '\n';return;}// 定义 check 函数,验证 k 是否合法auto check = [&](int x) {vector<int> c(n);for (int i = 0; i < n; i++) {c[i] = a[i] % x;}sort(all(c));return c == b;};// 枚举 diff 的因数for (int i = 1; i * i <= dif; i++) {if (dif % i == 0) {if (check(i)) {cout << i << '\n';return;}if (check(dif / i)) {cout << dif / i << '\n';return;}}}// 如果没有找到合法的 k,输出 -1cout << -1 << '\n';
}signed main() {ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr); // 加速输入输出int _ = 1; cin >> _; // 测试用例数量while (_--) solve(); // 处理每个测试用例
}
代码关键部分解析
sort
- 作用:对数组进行排序。
- 特性:在本题中,我们使用排序来判断数组 $ a $ 和 $ b $ 是否可以直接匹配。
差值计算
for (int i = 0; i < n; i++) {cin >> a[i];dif += a[i];
}
for (int i = 0; i < n; i++) {cin >> b[i];dif -= b[i];
}
- 作用:计算数组 $ a $ 和 $ b $ 的差值 $ \text{diff} $。
- 特性:如果 $ \text{diff} \leq 0 $,则说明找不到合法的 $ k $。
枚举因数
for (int i = 1; i * i <= dif; i++) {if (dif % i == 0) {if (check(i)) {cout << i << '\n';return;}if (check(dif / i)) {cout << dif / i << '\n';return;}}
}
- 作用:枚举 $ \text{diff} $ 的所有因数,并检查是否满足条件。
- 特性:通过因数分解减少枚举范围,提高效率。
检查函数
auto check = [&](int x) {vector<int> c(n);for (int i = 0; i < n; i++) {c[i] = a[i] % x;}sort(all(c));return c == b;
};
- 作用:验证给定的 $ k $ 是否合法。
- 特性:通过对数组 $ a $ 的每个元素取模并排序,判断是否与数组 $ b $ 相同。
示例分析
输入
5
4
3 5 2 7
0 1 1 1
5
3 1 5 2 4
1 2 3 4 5
6
2 3 4 7 8 9
1 2 3 6 7 8
5
21 22 25 28 20
0 1 2 1 0
6
1 1 2 3 5 8
0 0 1 1 0 0
输出
2
31415926
-1
4
-1
解释
-
第一个测试用例:
- $ a = [3, 5, 2, 7] \(,\) b = [0, 1, 1, 1] $。
- $ \text{diff} = 17 - 2 = 15 $。
- 枚举 $ \text{diff} $ 的因数 $ 1, 3, 5, 15 $,发现 $ k = 2 $ 合法。
- 输出 $ 2 $。
-
第二个测试用例:
- $ a = [3, 1, 5, 2, 4] \(,\) b = [1, 2, 3, 4, 5] $。
- $ \text{diff} = 15 - 15 = 0 $。
- 输出 $ 31415926 $(任意一个大于等于最大值的 $ k $)。
-
第三个测试用例:
- $ a = [2, 3, 4, 7, 8, 9] \(,\) b = [1, 2, 3, 6, 7, 8] $。
- 无法找到合法的 $ k $。
- 输出 $ -1 $。