atcoder ABC 459 题解

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 的首字符是 abc 之一,则 C i = C_i= Ci= 2
  • 如果 S i S_i Si 的首字符是 def 之一,则 C i = C_i= Ci= 3
  • 如果 S i S_i Si 的首字符是 ghi 之一,则 C i = C_i= Ci= 4
  • 如果 S i S_i Si 的首字符是 jkl 之一,则 C i = C_i= Ci= 5
  • 如果 S i S_i Si 的首字符是 mno 之一,则 C i = C_i= Ci= 6
  • 如果 S i S_i Si 的首字符是 pqrs 之一,则 C i = C_i= Ci= 7
  • 如果 S i S_i Si 的首字符是 tuv 之一,则 C i = C_i= Ci= 8
  • 如果 S i S_i Si 的首字符是 wxyz 之一,则 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. 类型 1: ( ± A , ± B ) (\pm A, \pm B) (±A,±B) 或 ( ± B , ± A ) (\pm B, \pm A) (±B,±A)
  2. 类型 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;
}
相关推荐
EllinY12 小时前
CF2217E Definitely Larger 题解
c++·笔记·算法·构造
玖釉-15 小时前
下一个排列:从字典序到原地算法的完整推导
数据结构·c++·windows·算法
IronMurphy15 小时前
【算法五十】62. 不同路径
算法
影寂ldy15 小时前
C#一维数组
算法
过期动态16 小时前
【LeetCode 热题 100】移动零
java·数据结构·算法·leetcode·职场和发展·rabbitmq
计算机安禾17 小时前
【算法分析与设计】第10篇:下界理论与NP完全性初步
大数据·人工智能·算法
水木流年追梦18 小时前
大模型入门-大模型分布式训练2
开发语言·分布式·python·算法·正则表达式·prompt
sali-tec18 小时前
C# 基于OpenCv的视觉工作流-章78-KRT测量
图像处理·人工智能·数码相机·opencv·算法·计算机视觉