atcoder abc #459 题解
A - Hell, World!
题目描述
给定一个 1 到 10 之间的整数 X。
输出从字符串 HelloWorld 中仅删除第 X 个字符后得到的字符串。
解题思路
我们把字符串 HelloWorld 存下来,然后遍历每个字符,跳过第 X 个字符(注意字符串下标从 0 开始,所以第 X 个字符对应下标 X-1),把其余字符输出即可。
代码
cpp
#include <bits/stdc++.h>
using namespace std;
int x; // 要删除的字符位置(1到10之间)
int main()
{
cin >> x; // 读取要删除的位置
string s = "HelloWorld"; // 原字符串
for (int i = 0; i < s.size(); i++) // 遍历每个字符
{
if (i == x - 1) // 如果是第X个字符(注意下标从0开始)
continue; // 跳过,不输出
cout << s[i]; // 否则输出该字符
}
return 0;
}
B - 459
题目描述
给定 N N N 个由小写英文字母组成的字符串 S 1 , S 2 , ... , S N S_1, S_2, \ldots, S_N S1,S2,...,SN。
按以下规则定义 N N N 个数字 C 1 , C 2 , ... , C N C_1, C_2, \ldots, C_N C1,C2,...,CN:
- 如果 S i S_i Si 的首字符是
a、b、c之一,则 C i = C_i= Ci=2 - 如果 S i S_i Si 的首字符是
d、e、f之一,则 C i = C_i= Ci=3 - 如果 S i S_i Si 的首字符是
g、h、i之一,则 C i = C_i= Ci=4 - 如果 S i S_i Si 的首字符是
j、k、l之一,则 C i = C_i= Ci=5 - 如果 S i S_i Si 的首字符是
m、n、o之一,则 C i = C_i= Ci=6 - 如果 S i S_i Si 的首字符是
p、q、r、s之一,则 C i = C_i= Ci=7 - 如果 S i S_i Si 的首字符是
t、u、v之一,则 C i = C_i= Ci=8 - 如果 S i S_i Si 的首字符是
w、x、y、z之一,则 C i = C_i= Ci=9
按顺序输出将 C 1 , C 2 , ... , C N C_1, C_2, \ldots, C_N C1,C2,...,CN 连接起来的字符串。
解题思路
这道题考察的是对手机九宫格键盘的映射。我们可以把每个小写字母映射到它对应的数字。用数组直接存储映射关系:'a' 到 'c' 对应 2,'d' 到 'f' 对应 3,以此类推。
我们只需要读取每个字符串,取出它的首字符,把字符减去 'a' 得到下标('a' 对应 0,'b' 对应 1,...),然后用这个下标去数组里查对应的数字并输出即可。
代码
cpp
#include <bits/stdc++.h>
using namespace std;
int main()
{
// 映射数组:a到z分别对应的数字(按手机九宫格)
int a[30] = {2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 9, 9, 9, 9};
int n;
cin >> n; // 读取字符串数量
for (int i = 0; i < n; i++)
{
string s;
cin >> s; // 读取字符串
int c = s[0] - 'a'; // 取首字符,转成数组下标(a=0, b=1, ...)
cout << a[c]; // 输出对应的数字
}
return 0;
}
C - Drop Blocks
题目描述
有 N N N 个单元格从左到右排成一排。初始时,所有单元格都没有放置方块。
给定 Q Q Q 个查询,请按顺序处理。每个查询是以下两种类型之一:
1 x:在从左数第 x 个单元格放置 1 个方块。然后,如果每个单元格都至少有 1 个方块,则从每个单元格移除 1 个方块。2 y:输出至少有 y y y 个方块的单元格数量。
解题思路
这道题的关键在于如何快速回答"至少有 y 个方块的单元格数量"这个查询。我们可以用一个计数数组 b 来维护这个信息。
我们用 a[x] 记录第 x 个单元格当前的方块数,用 b[i] 记录"方块数小于等于 i 的单元格数量"。初始时所有单元格都是 0 个方块,所以 b[i] = n 对所有 i。
当我们在第 x 个单元格加一个方块时,a[x] 从 k 变成 k+1。这意味着方块数小于等于 k 的单元格少了一个,所以我们让 b[k] 减 1。b[k+1] 及之后的值不变。
当所有单元格都至少有 1 个方块时(也就是 b[0] = 0),我们需要从每个单元格移除 1 个方块。这等价于所有 a[x] 减 1,b 数组整体左移一位。
对于查询"至少有 y 个方块",我们可以维护一个指针 c 指向最小的还有方块的单元格数量(也就是最小的 i 使得 b[i] > 0)。然后答案为 n - b[y + c - 1]。
代码
cpp
#include <bits/stdc++.h>
using namespace std;
const int maxn = 6e5 + 5;
int b[maxn], a[maxn], n, q, c = 0; // b[i]表示恰好有i个方块的单元格数,a[x]表示第x个单元格的方块数,c指向最小的还有方块的单元格数量
int main()
{
cin >> n >> q;
for (int i = 0; i < maxn; i++)
b[i] = n; // 初始时所有单元格都是0个方块,所以b[0]=n
while (q--)
{
int type, x;
cin >> type >> x;
if (type == 1)
{
b[a[x]]--; // 第x个单元格从a[x]个方块变为a[x]+1个
a[x]++; // 方块数加1
while (b[c] == 0) // 维护指针c
c++;
}
else
cout << n - b[x + c - 1] << '\n'; // 输出至少有x个方块的单元格数量
}
return 0;
}
D - Adjacent Distinct String
题目描述
给定一个由小写英文字母组成的字符串 S S S。
判断是否可以重新排列 S S S 的字符,使得没有两个相邻字符相同。如果可以,请找出一个这样的排列。
给定 T T T 组测试数据,请分别求解。
解题思路
贪心的题目。
首先,如果某个字符的出现次数超过了总长度的一半(向上取整),那么肯定无法排列,因为无法避免相邻。
否则,我们可以用一个最大堆来维护每个字符的剩余数量。每次取出数量最多的字符,如果它和结果字符串的最后一个字符相同,就取出第二多的字符。用完后如果还有剩余,就把它放回堆里。
这样可以保证每次都选择当前数量最多的字符,避免相邻重复。
代码
cpp
#include <bits/stdc++.h>
using namespace std;
void solve()
{
string s;
cin >> s;
priority_queue<pair<int, int>> q; // 最大堆,按字符出现次数排序
vector<int> num(30, 0); // 统计每个字符的出现次数
for (char c : s)
num[c - 'a']++;
for (int i = 0; i < 26; i++)
{
if (num[i] == 0)
continue;
q.push({num[i], i}); // 把字符和它的数量压入堆
}
string ans = "!"; // 初始字符,避免第一个字符判断出错
while (!q.empty())
{
auto [x, y] = q.top(); // 取出数量最多的字符
q.pop();
if (*(ans.end() - 1) == char(y + 'a')) // 如果和最后一个字符相同
{
if (q.empty()) // 如果没有其他字符可用,说明无法排列
{
cout << "No\n";
return;
}
auto [_x, _y] = q.top(); // 取出第二多的字符
q.pop();
ans += char(_y + 'a'); // 添加到结果
q.push({x, y}); // 把刚才那个字符放回堆
if (_x > 1) // 如果还有剩余
q.push({_x - 1, _y}); // 放回堆
}
else // 如果和最后一个字符不同,可以直接用
{
ans += char(y + 'a');
if (x > 1) // 如果还有剩余
q.push({x - 1, y}); // 放回堆
}
}
cout << "Yes\n";
cout << ans.substr(1) << '\n'; // 去掉初始的"!"
return;
}
int main()
{
int T;
cin >> T;
while (T--)
solve();
return 0;
}
E - Select from Subtrees
题目描述
有一棵包含 N N N 个顶点的有根树 T T T,顶点编号为 1 , 2 , ... , N 1, 2, \ldots, N 1,2,...,N。
顶点 1 1 1 是 T T T 的根,顶点 i i i( 2 ≤ i ≤ N 2 \leq i \leq N 2≤i≤N)的直接父节点是 P i P_i Pi。
此外,顶点 i i i( 1 ≤ i ≤ N 1 \leq i \leq N 1≤i≤N)有 C i C_i Ci 颗糖果。所有 ( C 1 + C 2 + ⋯ + C N ) (C_1 + C_2 + \cdots + C_N) (C1+C2+⋯+CN) 颗糖果都是互不相同的。
Takahashi 给 N N N 只松鼠下达了指令。具体来说,他给第 i i i 只松鼠( 1 ≤ i ≤ N 1 \leq i \leq N 1≤i≤N)下达了以下指令:
- 从以顶点 i i i 为根的子树中选择并收集 D i D_i Di 颗糖果。
不同的松鼠不能拿相同的糖果。
输出可能的选法数量,对 998244353 998244353 998244353 取模。
注意,即使最终选择的糖果集合相同,但如果选择它们的松鼠不同,也被视为不同的选法。
如果不可能让所有松鼠都按指令带回糖果,则输出 0 0 0。
解题思路
树形 dp。我们从叶子节点向上处理,对于每个节点,我们需要计算从其子树中选 D_i 颗糖果的方案数。
每个节点的子树包含它自己的糖果和所有后代节点的糖果。当子节点选完糖果后,剩下的糖果才能被父节点选择。
我们用 ans[x] 表示以 x 为根的子树的方案数。对于节点 x,我们先处理它的所有子节点,然后从剩余的糖果中选择 D_x 颗。dfs 函数返回的是该子树中未被选择的糖果数量。
代码
cpp
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int maxn = 1e6 + 5, mod = 998244353;
int n, c[maxn], d[maxn], ans[maxn], fac[maxn], inv_fac[maxn]; // c[i]是糖果数,d[i]是要选的数量,ans[i]是方案数
vector<int> e[maxn]; // 邻接表存树
int quick_pow(int a, int b) // 快速幂计算 a^b mod mod
{
int ans = 1;
while (b)
{
if (b & 1)
ans = ans * a % mod;
a = a * a % mod;
b >>= 1;
}
return ans;
}
int C(int a, int b) // 计算组合数 C(a, b)
{
if (a < b || b < 0)
return 0;
int res = 1;
for (int i = 0; i < b; i++)
res = res * ((a - i) % mod) % mod;
res = res * inv_fac[b] % mod;
return res;
}
int dfs(int x, int fa) // 深度优先搜索,返回子树中未被选走的糖果数量
{
ans[x] = 1; // 初始方案数为1
int res = c[x]; // 当前节点的糖果数
for (int v : e[x]) // 遍历所有子节点
{
if (v == fa)
continue;
res += dfs(v, x); // 累加子树中未被选走的糖果
(ans[x] *= ans[v]) %= mod; // 方案数相乘
}
(ans[x] *= C(res, d[x])) %= mod; // 从res颗糖果中选d[x]颗
return res - d[x]; // 返回剩余的糖果数量
}
signed main()
{
cin >> n;
fac[0] = 1;
for (int i = 1; i < maxn; i++)
fac[i] = fac[i - 1] * i % mod; // 预处理阶乘
inv_fac[maxn - 1] = quick_pow(fac[maxn - 1], mod - 2); // 预处理逆阶乘
for (int i = maxn - 2; i >= 0; i--)
inv_fac[i] = inv_fac[i + 1] * (i + 1) % mod;
for (int i = 2; i <= n; i++) // 读入树的边
{
int fa;
cin >> fa;
e[i].push_back(fa);
e[fa].push_back(i);
}
for (int i = 1; i <= n; i++)
cin >> c[i]; // 读入每个节点的糖果数
for (int i = 1; i <= n; i++)
cin >> d[i]; // 读入每个松鼠要选的数量
dfs(1, 0); // 从根节点开始处理
cout << ans[1]; // 输出答案
return 0;
}
F - -1, +1
题目描述
给定一个长度为 N N N 的非负整数序列 A = ( A 1 , A 2 , ... , A N ) A = (A_1, A_2, \ldots, A_N) A=(A1,A2,...,AN)。
可以对 A A A 执行以下操作任意次数:
- 选择一个整数 i i i( 1 ≤ i ≤ N − 1 1 \le i \le N - 1 1≤i≤N−1),将 A i A_i Ai 减 1, A i + 1 A_{i+1} Ai+1 加 1。
求使得 A A A 严格递增所需的最小操作次数。
可以证明答案小于 2 63 2^{63} 263。
给定 T T T 组测试数据,请分别求解。
解题思路
每次操作相当于把一个"单位"从位置 i i i 移动到位置 i + 1 i+1 i+1,操作次数就是单位移动的总距离。例如,把一个单位从位置 1 移到位置 3 需要 2 次操作(1→2,2→3)。
设最终序列为 B B B,要求 B 1 < B 2 < ⋯ < B N B_1 < B_2 < \cdots < B_N B1<B2<⋯<BN。我们需要找到满足条件的 B B B,使得从 A A A 到 B B B 的操作次数最少。
前缀和不变性 :操作不改变任何前缀和。设前缀和 P i = A 1 + A 2 + ⋯ + A i P_i = A_1 + A_2 + \cdots + A_i Pi=A1+A2+⋯+Ai,则最终序列的前缀和也等于 P i P_i Pi,即:
B 1 + B 2 + ⋯ + B i = P i B_1 + B_2 + \cdots + B_i = P_i B1+B2+⋯+Bi=Pi
严格递增条件 :因为 B B B 是严格递增的,所以 B i ≥ B i − 1 + 1 B_i \geq B_{i-1} + 1 Bi≥Bi−1+1。递推可得:
B i ≥ B 1 + ( i − 1 ) B_i \geq B_1 + (i-1) Bi≥B1+(i−1)
将这个不等式对前 i i i 项求和:
P i = B 1 + B 2 + ⋯ + B i ≥ i ⋅ B 1 + ( 0 + 1 + 2 + ⋯ + ( i − 1 ) ) P_i = B_1 + B_2 + \cdots + B_i \geq i \cdot B_1 + (0 + 1 + 2 + \cdots + (i-1)) Pi=B1+B2+⋯+Bi≥i⋅B1+(0+1+2+⋯+(i−1))
P i ≥ i ⋅ B 1 + i ( i − 1 ) 2 P_i \geq i \cdot B_1 + \frac{i(i-1)}{2} Pi≥i⋅B1+2i(i−1)
整理得:
B 1 ≤ P i − i ( i − 1 ) 2 i B_1 \leq \frac{P_i - \frac{i(i-1)}{2}}{i} B1≤iPi−2i(i−1)
对于所有 i i i, B 1 B_1 B1 必须满足这个不等式。所以 B 1 B_1 B1 的最大值为:
B 1 = min 1 ≤ i ≤ N ⌊ P i − i ( i − 1 ) 2 i ⌋ B_1 = \min_{1 \leq i \leq N} \left\lfloor \frac{P_i - \frac{i(i-1)}{2}}{i} \right\rfloor B1=1≤i≤Nmin⌊iPi−2i(i−1)⌋
令 c i = B i − i c_i = B_i - i ci=Bi−i,则 B i = c i + i B_i = c_i + i Bi=ci+i。由于 B i < B i + 1 B_i < B_{i+1} Bi<Bi+1,代入得:
c i + i < c i + 1 + ( i + 1 ) c_i + i < c_{i+1} + (i+1) ci+i<ci+1+(i+1)
c i ≤ c i + 1 c_i \leq c_{i+1} ci≤ci+1
即 c c c 是非递减序列。同时,前缀和条件变为:
∑ k = 1 i ( c k + k ) = P i \sum_{k=1}^i (c_k + k) = P_i k=1∑i(ck+k)=Pi
∑ k = 1 i c k = P i − i ( i + 1 ) 2 = u i \sum_{k=1}^i c_k = P_i - \frac{i(i+1)}{2} = u_i k=1∑ick=Pi−2i(i+1)=ui
我们的目标是找到非递减序列 c c c,使得 ∑ k = 1 i c k = u i \sum_{k=1}^i c_k = u_i ∑k=1ick=ui 对所有 i i i 成立。这可以通过维护上凸壳来高效求解。
操作次数计算 :操作次数等于所有单位移动距离的总和。对于前 i i i 个位置,操作次数为:
∑ k = 1 i ( A k − B k ) × ( i − k ) \sum_{k=1}^i (A_k - B_k) \times (i - k) k=1∑i(Ak−Bk)×(i−k)
这个式子可以简化为:
∑ k = 1 i ( A k − B k ) × ( i − k ) = ∑ k = 1 i ∑ j = k i − 1 ( A k − B k ) \sum_{k=1}^i (A_k - B_k) \times (i - k) = \sum_{k=1}^i \sum_{j=k}^{i-1} (A_k - B_k) k=1∑i(Ak−Bk)×(i−k)=k=1∑ij=k∑i−1(Ak−Bk)
= ∑ j = 1 i − 1 ∑ k = 1 j ( A k − B k ) = \sum_{j=1}^{i-1} \sum_{k=1}^j (A_k - B_k) =j=1∑i−1k=1∑j(Ak−Bk)
即前 i − 1 i-1 i−1 个位置的前缀和之差的总和。
代码
cpp
#include <bits/stdc++.h>
#define int long long
using namespace std;
using i128 = __int128_t; // 使用__int128避免溢出
signed main()
{
int t;
cin >> t;
while (t--)
{
int n;
cin >> n;
vector<int> a(n + 1), p(n + 1), u(n + 1);
for (int i = 1; i <= n; i++)
{
cin >> a[i];
p[i] = p[i - 1] + a[i]; // p[i]是前缀和
// u[i] = p[i] - i*(i+1)/2,用于构建凸包
u[i] = p[i] - i * (i + 1) / 2;
}
vector<int> js; // 维护上凸壳的点的下标
for (int i = 0; i <= n; i++)
{
// 维护上凸壳,移除不满足条件的点
while (js.size() >= 2)
{
int a = js[js.size() - 2], b = js.back();
// 比较斜率,判断是否需要移除b点
if ((i128)(u[b] - u[a]) * (i - b) >= (i128)(u[i] - u[b]) * (b - a))
{
js.pop_back();
}
else
{
break;
}
}
js.push_back(i);
}
vector<int> c(n + 1); // c[i] = B[i] - i
for (int i = 0; i < (int)js.size() - 1; i++)
{
int l = u[js[i + 1]] - u[js[i]];
int r = js[i + 1] - js[i];
int base = (int)floor(l * 1.0 / r); // 向下取整
int now = l - base * r; // 余数
// 分配c数组的值,尽量平均分布
for (int j = 1; j <= r; j++)
{
if (j <= r - now)
{
c[js[i] + j] = base;
}
else
{
c[js[i] + j] = base + 1;
}
}
}
// 计算操作次数
int ans = 0, suma = 0, sumb = 0;
for (int i = 1; i < n; i++)
{
suma += a[i]; // A的前缀和
sumb += c[i] + i; // B的前缀和(B[i] = c[i] + i)
ans += suma - sumb; // 操作次数 = A前缀和 - B前缀和
}
cout << ans << '\n';
}
return 0;
}
G - Golf 2
题目描述
给定整数 A , B , X , Y A, B, X, Y A,B,X,Y。
在二维平面上放置一个棋子。初始时,棋子位于坐标 ( 0 , 0 ) (0, 0) (0,0)。
可以执行以下操作任意次数:
- 设 ( x , y ) (x, y) (x,y) 为棋子当前的坐标。将棋子移动到坐标 ( x ′ , y ′ ) (x', y') (x′,y′),满足以下条件之一:
- ∣ x − x ′ ∣ = A |x - x'| = A ∣x−x′∣=A 且 ∣ y − y ′ ∣ = B |y - y'| = B ∣y−y′∣=B
- ∣ x − x ′ ∣ = B |x - x'| = B ∣x−x′∣=B 且 ∣ y − y ′ ∣ = A |y - y'| = A ∣y−y′∣=A
判断是否可以将棋子移动到坐标 ( X , Y ) (X, Y) (X,Y),如果可以,求所需的最小操作次数。
给定 T T T 组测试数据,请分别求解。
解题思路
这道题需要数学建模来解决。每次移动有两种类型:
- 类型 1: ( ± A , ± B ) (\pm A, \pm B) (±A,±B) 或 ( ± B , ± A ) (\pm B, \pm A) (±B,±A)
- 类型 2: ( ± A , ∓ B ) (\pm A, \mp B) (±A,∓B) 或 ( ± B , ∓ A ) (\pm B, \mp A) (±B,∓A)
设进行了 p p p 次类型 1 操作和 q q q 次类型 2 操作,每次操作在 x 和 y 方向的移动可以是正负的。
经过一系列操作后,最终坐标满足:
X = ( a 1 + a 2 + ⋯ + a p + q ) X = (a_1 + a_2 + \cdots + a_{p+q}) X=(a1+a2+⋯+ap+q)
Y = ( b 1 + b 2 + ⋯ + b p + q ) Y = (b_1 + b_2 + \cdots + b_{p+q}) Y=(b1+b2+⋯+bp+q)
其中每个 ( a i , b i ) (a_i, b_i) (ai,bi) 是一次移动,要么是 ( A , B ) , ( − A , B ) , ( A , − B ) , ( − A , − B ) (A,B), (-A,B), (A,-B), (-A,-B) (A,B),(−A,B),(A,−B),(−A,−B)(类型1),要么是 ( A , − B ) , ( − A , − B ) , ( B , A ) , ( − B , A ) (A,-B), (-A,-B), (B,A), (-B,A) (A,−B),(−A,−B),(B,A),(−B,A)(类型2)。
扩展欧几里得算法的应用:
设 d = gcd ( A , B ) d = \gcd(A, B) d=gcd(A,B),则 X X X 和 Y Y Y 都必须是 d d d 的倍数,否则无解。这是因为每次移动的坐标变化都是 A A A 和 B B B 的线性组合,而 d d d 是它们的最大公约数。
将所有数除以 d d d 后, A A A 和 B B B 互质。此时存在整数 x 0 , y 0 x_0, y_0 x0,y0 满足:
A ⋅ x 0 + B ⋅ y 0 = 1 A \cdot x_0 + B \cdot y_0 = 1 A⋅x0+B⋅y0=1
这就是扩展欧几里得算法要找的贝祖系数。
构造解:
我们需要找到四个整数 w , x , y , z w, x, y, z w,x,y,z 表示四种方向的操作次数,满足:
( w − x ) ⋅ A + ( y − z ) ⋅ B = X (w - x) \cdot A + (y - z) \cdot B = X (w−x)⋅A+(y−z)⋅B=X
( w − x ) ⋅ B + ( y − z ) ⋅ A = Y (w - x) \cdot B + (y - z) \cdot A = Y (w−x)⋅B+(y−z)⋅A=Y
操作次数为 w + x + y + z w + x + y + z w+x+y+z。
通过变量替换和化简,我们可以将问题转化为求解线性方程组。代码中枚举了边界情况(c1 和 c2),然后通过克拉默法则求解,最后在解附近枚举整数点找到最优解。
关键公式:
C x = X − c 1 ⋅ A − c 2 ⋅ B C_x = X - c1 \cdot A - c2 \cdot B Cx=X−c1⋅A−c2⋅B
C y = Y − c 1 ⋅ B − c 2 ⋅ A C_y = Y - c1 \cdot B - c2 \cdot A Cy=Y−c1⋅B−c2⋅A
其中 c 1 , c 2 ∈ { 0 , 1 } c1, c2 \in \{0, 1\} c1,c2∈{0,1} 是边界调整参数。如果 C x C_x Cx 或 C y C_y Cy 是奇数,则当前组合无解。
最终操作次数由四个方向的操作数的绝对值的最大值之和决定:
操作次数 = max ( ∣ f 0 ∣ , ∣ f 1 ∣ ) + max ( ∣ f 2 ∣ , ∣ f 3 ∣ ) \text{操作次数} = \max(|f0|, |f1|) + \max(|f2|, |f3|) 操作次数=max(∣f0∣,∣f1∣)+max(∣f2∣,∣f3∣)
代码
cpp
#include <bits/stdc++.h>
#define int long long
using namespace std;
const long long INF = 9e18;
long long A, B, X, Y;
long long v0, v1, v2, v3; // 用于存储中间变量
long long ans; // 最小操作次数
// 扩展欧几里得算法,求解 ax + by = gcd(a, b)
void exgcd(long long a, long long b, long long &d, long long &x, long long &y)
{
if (!b)
{
d = a;
x = 1;
y = 0;
return;
}
exgcd(b, a % b, d, y, x);
y -= (a / b) * x;
}
// 更新答案,计算当前解对应的操作次数
void update(long long x, long long y)
{
// 根据参数计算四个方向的操作次数
long long f0 = 2 * B * x + v0;
long long f1 = 2 * A * y + v1;
long long f2 = 2 * A * x + v2;
long long f3 = 2 * B * y + v3;
// 取最大值之和作为操作次数
ans = min(ans, max(abs(f0), abs(f1)) + max(abs(f2), abs(f3)));
}
// 带符号的向下取整除法
long long floor_div(long long a, long long b)
{
if(b < 0)
{
a = -a;
b = -b;
}
if(a >= 0)
return a / b;
return -((-a + b - 1) / b);
}
// 解线性方程组,找到满足条件的整数解
void solve(long long a00, long long a01, long long b0, long long a10, long long a11, long long b1)
{
long long det = a00 * a11 - a01 * a10; // 计算行列式
if(!det) // 行列式为0,无解或无穷多解
return;
// 用克拉默法则求解
long long nx = b0 * a11 - a01 * b1;
long long ny = a00 * b1 - b0 * a10;
long long x0 = floor_div(nx, det);
long long y0 = floor_div(ny, det);
// 枚举附近的整数点,寻找最优解
for(int i = -3; i <= 3; i++)
{
for(int j = -3; j <= 3; j++)
{
update(x0 + i, y0 + j);
}
}
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
int T;
cin >> T;
while (T--)
{
cin >> A >> B >> X >> Y;
long long d, x0, y0;
exgcd(A, B, d, x0, y0); // 计算gcd和贝祖系数
if (X % d || Y % d) // 如果X或Y不是d的倍数,无解
{
cout << "-1\n";
continue;
}
// 约简,使A和B互质
A /= d;
B /= d;
X /= d;
Y /= d;
ans = INF;
// 枚举边界情况
for (int c1 = 0; c1 <= 1; c1++)
{
for (int c2 = 0; c2 <= 1; c2++)
{
long long Cx = X - c1 * A - c2 * B;
long long Cy = Y - c1 * B - c2 * A;
if ((Cx % 2) || (Cy % 2)) // 必须是偶数
continue;
// 计算中间变量
long long v0_local = Cx / 2 * x0;
long long v1_local = Cx / 2 * y0;
long long v2_local = Cy / 2 * y0;
long long v3_local = Cy / 2 * x0;
::v0 = 2 * v0_local + c1;
::v1 = 2 * v2_local + c1;
::v2 = -2 * v1_local - c2;
::v3 = -2 * v3_local - c2;
// 尝试多种组合
solve(2 * B, -2 * A, ::v1 - ::v0, 2 * B, 2 * A, -::v1 - ::v0);
solve(2 * A, -2 * B, ::v3 - ::v2, 2 * A, 2 * B, -::v3 - ::v2);
solve(2 * B, -2 * A, ::v1 - ::v0, 2 * A, 2 * B, -::v3 - ::v2);
solve(2 * A, -2 * B, ::v3 - ::v2, 2 * B, 2 * A, -::v1 - ::v0);
solve(2 * B, -2 * A, ::v1 - ::v0, 2 * A, -2 * B, ::v3 - ::v2);
solve(2 * B, 2 * A, -::v1 - ::v0, 2 * A, 2 * B, -::v3 - ::v2);
}
}
if (ans == INF) // 无解
cout << "-1\n";
else
cout << ans << "\n";
}
return 0;
}