atcoder ABC 454 题解

atcoder abc 454

A - Closed interval

问题陈述

你得到整数 LLL 和 RRR 。

找出在 LLL 和 RRR 之间有多少个整数。

解题思路

大水题,用减法或者枚举都可以求解答案,直接输出即可。

代码

cpp 复制代码
#include <bits/stdc++.h>

using namespace std;

int main()
{
    int l, r;
    cin >> l >> r;
    cout << r - l + 1 << '\n';
    return 0;
}

B - Mapping

问题陈述

编号为 111 到 NNN 的人有 NNN 。

编号为 111 到 MMM 的衣服有 MMM 种。人 iii 穿的衣服 FiF_iFi 。

YesNo 回答以下两个问题。

问题 111 :所有的 NNN 人都穿着不同类型的衣服吗?

问题 222 :对于每一种 MMM 类型的衣服,是否至少有一个人穿着该类型的衣服?

解题思路

开辟一个数组,记录每一种衣服是否出现过。接下来维护出现过的衣服种类数量即可,具体实现看代码。

代码

cpp 复制代码
#include <bits/stdc++.h>

using namespace std;

int n, m, vis[105];  // vis数组标记某类衣服是否出现过,大小为105以容纳最多100种衣服

int main()
{
    cin >> n >> m;
    int f1, f2, cnt = 0;  // f1表示问题1的答案,f2表示问题2的答案,cnt记录不同衣服种类的数量
    f1 = f2 = 1;           // 先假设两个问题的答案都是Yes(1表示Yes,0表示No)

    for (int i = 1; i <= n; i++)
    {
        int x;             // 第i个人穿的衣服类型编号
        cin >> x;
        if (!vis[x])       // 如果这种衣服之前没出现过
            vis[x] = 1, cnt++;  // 标记为已出现,不同种类数+1
        else
            f1 = 0;        // 出现重复衣服 → 问题1答案为No(不是所有人都穿不同类型)
    }

    if (cnt != m)          // 如果收集到的衣服种类数不等于总种类数M
        f2 = 0;            // 问题2答案为No(存在某种衣服没人穿)

    // 输出问题1的答案
    if (f1)
        cout << "Yes\n";
    else
        cout << "No\n";

    // 输出问题2的答案
    if (f2)
        cout << "Yes\n";
    else    
        cout << "No\n";

    return 0;
}

C - Straw Millionaire

问题描述

有 NNN 种物品,编号为物品 111 到物品 NNN。初始时,高桥只有物品 111。

他有 MMM 个朋友。对于第 iii 个朋友(1≤i≤M1 \le i \le M1≤i≤M),如果高桥把物品 AiA_iAi 送给这个朋友,他就会获得物品 BiB_iBi。

求高桥能获得的物品种类数(包括初始就有的物品 111)。

解题思路

首先转换问题模型,一个朋友可以把物品 AiA_iAi 换成 BiB_iBi 就相当于在 AiA_iAi 和 BiB_iBi 之间连了一条单向边,从 1 号点出发,看看可以抵达哪些点就可以了。

代码

cpp 复制代码
#include <bits/stdc++.h>

using namespace std;

const int maxn = 3e5 + 5;
int n, m, vis[maxn], ans;        // vis标记某个物品是否已获得,ans记录能获得的物品种类数
vector<int> e[maxn];             // 邻接表存储交换关系:e[u] 表示送出物品u后能获得的所有物品

// 深度优先搜索:从物品x出发,收集所有能间接获得的物品
void dfs(int x)
{
    if (vis[x])                  // 如果这个物品已经获得过了,不再重复处理
        return;
    vis[x] = 1;                  // 标记物品x为已获得
    ans++;                       // 物品种类数加1
    // 遍历所有从x出发能直接换到的物品
    for (int i = 0; i < e[x].size(); i++)
        dfs(e[x][i]);            // 递归获得这些物品后,还能继续换其他物品
}

int main()
{
    cin >> n >> m;
    // 读入M条交换规则
    for (int i = 1; i <= m; i++)
    {
        int u, v;
        cin >> u >> v;
        e[u].push_back(v);       // 送出物品u,可获得物品v(有向边 u -> v)
    }
    dfs(1);                      // 从初始拥有的物品1开始搜索
    cout << ans;                 // 输出最终能获得的物品种类数(包括物品1本身)
    return 0;
}

D - (xx)

问题描述

给你一个由 (, x, ) 组成的字符串 AAA。

你可以对 AAA 进行任意多次以下两种操作,顺序不限:

  • 选择 AAA 中的一个子串 (xx),并将其替换为 xx
  • 选择 AAA 中的一个子串 xx,并将其替换为 (xx)

再给你一个由 (, x, ) 组成的字符串 BBB。请判断能否通过若干次操作将 AAA 变成 BBB。

共有 TTT 组测试数据,请对每组数据进行判断。

解题思路

可以把题目理解为,只要有 xx 就可以在他们的两边加/删括号,我们尽可能删除 AB 里面所有的括号,如果删除之后两个字符串一样,就说明可以转换。

具体实现看代码。

代码

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

const int maxn = 2e6 + 5;
string A, B;
int vis_A[maxn], vis_B[maxn];   // 标记可删除的括号(即被 xx 包裹的括号)

bool solve()
{
    cin >> A >> B;

    // 处理字符串 A:找到所有 "xx" 并尝试向两边扩展匹配 '(' 和 ')'
    for (int i = 0; i < A.size() - 1; i++)
    {
        // 找到连续的 "xx"
        if (A[i] == A[i + 1] && A[i] == 'x')
        {
            int l = i - 1, r = i + 2;   // 左右各扩展一位,形成 "(xx)"
            // 尝试不断向外扩展,只要外层是 '(' 和 ')' 就标记这对括号为可删除
            while (l >= 0 && r < A.size() && A[l] == '(' && A[r] == ')')
                vis_A[l--] = 1, vis_A[r++] = 1;   // 标记这对括号
        }
    }

    // 对字符串 B 做完全相同处理
    for (int i = 0; i < B.size() - 1; i++)
    {
        if (B[i] == B[i + 1] && B[i] == 'x')
        {
            int l = i - 1, r = i + 2;
            while (l >= 0 && r < B.size() && B[l] == '(' && B[r] == ')')
                vis_B[l--] = 1, vis_B[r++] = 1;
        }
    }

    // 跳过所有被标记为可删除的括号,比较剩下的字符是否相同
    int i = 0, j = 0;
    while (i < A.size() && j < B.size())
    {
        // 跳过 A 中要删除的括号
        while (i < A.size() && vis_A[i])
            i++;
        // 跳过 B 中要删除的括号
        while (j < B.size() && vis_B[j])
            j++;
        if (i >= A.size() || j >= B.size())
            break;
        // 如果剩余字符不同,则不可转换
        if (A[i] != B[j])
        {
            // 清空标记数组(防止下一组测试干扰)
            for (int k = 0; k < A.size(); k++) vis_A[k] = 0;
            for (int k = 0; k < B.size(); k++) vis_B[k] = 0;
            return false;
        }
        i++, j++;
    }

    // 跳过末尾可能剩余的标记括号
    while (i < A.size() && vis_A[i]) i++;
    while (j < B.size() && vis_B[j]) j++;

    // 清空标记数组
    for (int k = 0; k < A.size(); k++) vis_A[k] = 0;
    for (int k = 0; k < B.size(); k++) vis_B[k] = 0;

    // 两个字符串都被完全匹配(剩余部分相同且无未匹配字符)
    return (i == A.size() && j == B.size());
}

int main()
{
    int t;
    cin >> t;
    while (t--)
    {
        if (solve())
            cout << "Yes\n";
        else
            cout << "No\n";
    }
    return 0;
}

E - LRUD Moving

问题描述

给定正整数 N,A,BN, A, BN,A,B。保证 (A) 和 (B) 都在 (1) 到 (N) 之间(包含)。

有一个 N×NN \times NN×N 的网格。从上数第 iii 行、从左数第 jjj 列的格子记为 (i,j)(i, j)(i,j)。初始时,一个棋子放在格子 (1,1)(1, 1)(1,1) 上。

通过重复 N2−2N^2 - 2N2−2 次移动,每次将棋子移动到相邻的格子(上、下、左、右),你希望让棋子最终到达格子 (N,N)(N, N)(N,N),并且在此过程中要经过除 (A,B)(A, B)(A,B) 以外的所有格子。你不能重复访问同一个格子(中途也不能访问 (1,1)(1, 1)(1,1) 和 (N,N)(N, N)(N,N) 之外的格子多次)。

判断是否存在这样的移动序列。如果存在,请输出其中一种。

一共有 TTT 组测试数据,你需要对每组数据进行求解。

解题思路

首先我们可以把所有的格子进行分类(应该所有人都会吧),(i+j)(i + j)(i+j) 为奇数的话我们就把他染成黑色格子,否则就是白色格子,在移动的过程当中按照 白 -> 黑 -> 白 -> 黑 ... 这样的顺序进行移动。

如果 NNN 为奇数,那么 N2−2N^2 - 2N2−2 也为奇数,又因为 (1,1)(1, 1)(1,1) 和 (N,N)(N, N)(N,N) 同色,所以不存在合法的方案。接下来我们再来看一下 aaa 和 bbb,对结果的影响,如果 a+ba + ba+b 为偶数,那么此时整个棋盘上的格子 (i,j)(i, j)(i,j) 为偶数的数量小于奇数的数量,但是实际上应该是偶数比奇数多一个(因为起点和终点都是偶数),所以 (a+b)(a + b)(a+b) 必须为奇数。

剩下的就是构造的问题了,我们要想出一个方法找路径。

因为此时 nnn 为偶数,所以我们直接两行两行的处理,先从左到右,再从右到左,直到遇到有障碍物的两行,变成上下移动去绕开障碍物,后面我们就变成从右往左,再从左往右就好了,最终保证可以到达 (n,n)(n, n)(n,n)。

代码

cpp 复制代码
#include <algorithm>
#include <cassert>
#include <iostream>
#include <vector>

using namespace std;

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int T;
    cin >> T;
    while (T--)
    {
        int n, a, b;
        cin >> n >> a >> b;
        a--;  // 转换为0-based索引,方便奇偶判断
        b--;

        // 不可行条件:
        // 1. n为奇数时,起点(0,0)和终点(n-1,n-1)同色,去掉一个格子后奇偶性不匹配
        // 2. (a+b)偶,即障碍物与起点同色,也会破坏哈密顿路径的二分图平衡
        if (n % 2 == 1 || (a + b) % 2 == 0)
        {
            cout << "No\n";
            continue;
        }

        vector<string> s1, s2;  // s1存储前半部分路径,s2存储后半部分(待反转)

        // 第一阶段:处理行方向的大循环(上下移动)
        // 共n/2 - 1个完整的"蛇形"行对
        for (int i = 0; i < n / 2 - 1; ++i)
        {
            // 标准行扫描模式:向右走n-1步,向下1步,向左n-1步,向下1步
            string s;
            s += string(n - 1, 'R');  // 向右到底
            s += 'D';                 // 向下
            s += string(n - 1, 'L');  // 向左到底
            s += 'D';                 // 再向下

            // 根据障碍物a(行坐标)决定将这段路径放入s1还是s2
            if (a >= 2)
            {
                s1.push_back(s);  // 障碍物还在后面,正常加入
                a -= 2;           // 已经处理了两行,减少a的相对位置
            }
            else
            {
                // 障碍物即将出现在接下来的两行中,需要反转这段路径
                reverse(s.begin(), s.end());
                s2.push_back(s);  // 放入后半部分,之后会整体反转
            }
        }

        // 第二阶段:处理列方向的小循环(左右微调),用于避开障碍物(b)
        for (int i = 0; i < n / 2 - 1; ++i)
        {
            // 小菱形绕行模式:下、右、上、右
            string s = "DRUR";
            if (b >= 2)
            {
                s1.push_back(s);  // 正常加入
                b -= 2;           // 已处理两列
            }
            else
            {
                reverse(s.begin(), s.end());
                s2.push_back(s);
            }
        }

        // 此时a和b应只剩下(0,1)或(1,0)的情况(即障碍物在最后需要微调的位置)
        assert((a == 0 && b == 1) || (a == 1 && b == 0));

        // 最后两步处理:根据障碍物相对位置决定最后绕行方向
        if (a == 0 && b == 1)
        {
            s1.push_back("DR");  // 先下后右
        }
        else
        {
            s1.push_back("RD");  // 先右后下
        }

        // 构建最终答案
        string ans;
        for (auto &x : s1)
            ans += x;               // 前半部分正序
        reverse(s2.begin(), s2.end());  // 反转后半部分的顺序
        for (auto &x : s2)
            ans += x;               // 后半部分加入(相当于整体路径的后半段)

        cout << "Yes\n" << ans << '\n';
    }
    return 0;
}

F - Make it Palindrome 2

问题描述

给定一个正整数 NNN 、一个正整数 MMM 和一个长度为 NNN 的整数序列 A=(A1,A2,...,AN)A=(A_1,A_2,\dots,A_N)A=(A1,A2,...,AN) 。

保证 AAA 的每个元素都在 000 和 M−1M-1M−1 之间。

您可以对整数序列 AAA 执行以下操作,次数不限,可能为零:

---选择一对满足 1≤l≤r≤N1\le l\le r\le N1≤l≤r≤N 的整数 (l,r)(l,r)(l,r) ,每个 i=l,l+1,...,ri=l,l+1,\ldots,ri=l,l+1,...,r 用 (Ai+1) mod M(A_{i+1}) \bmod M(Ai+1)modM 替换 AiA_iAi 。

求生成 AAA 一个回文所需的最小运算次数。

这里,如果 Ai=AN+1−iA_i=A_{N+1-i}Ai=AN+1−i ,i=1,2,...,Ni=1,2,\ldots,Ni=1,2,...,N ,则 AAA 是回文。

给你 TTT 测试用例;每个都解出来。

解题思路

在 A 的基础上我们定义一个新的数组 B,令 Bi=Ai−An−i+1,i∈[1,n/2]B_i = A_i - A_{n - i + 1},i \in [1, n / 2]Bi=Ai−An−i+1,i∈[1,n/2],通过贪心我们也可以得出,修改的区间不可能跨越中间,所以我们只用考虑前一半就可以了。

现在问题变成了,每一次我们都可以选择一个区间,让里面的元素全部加一或者减一,直到全部变成 0 位置,这个操作我们自然而然可以选择差分来完成。

所以我们就有了 Ci=Bi−Bi−1,i∈[1,n/2],Cn/2+1=0−B[n/2]C_i = B_i - B_{i-1}, i \in [1, n / 2], C_{n / 2 + 1} = 0 - B[n / 2]Ci=Bi−Bi−1,i∈[1,n/2],Cn/2+1=0−B[n/2]。

现在问题就变成了,我们可以让 C 里面的元素一个加一,一个减一,最终所有元素之和为 0,而且加一减一不会应该元素和模 m 的值,所以一开始的时候所有的 C 加起来一定是 m 的倍数。

假设我们让前 k 个最小的元素减一,剩下的加一,知道全部模 m 等于 0 为止。

这里我们令所有的 C 加起来为 S,前 k 个元素和为 X,那么就有了下面的公式:

X=(n/2+1−k)⋅m−S+X(n/2+1−k)⋅m−S=0k=n/2+1−S/mX = (n / 2 + 1 - k)·m - S + X \\ (n / 2 + 1 - k)·m - S = 0 \\ k = n / 2 + 1 - S / mX=(n/2+1−k)⋅m−S+X(n/2+1−k)⋅m−S=0k=n/2+1−S/m

然后计算前 k 个最小的元素之和是多少,然后输出即可。

代码

cpp 复制代码
#include <bits/stdc++.h>
#define int long long   // 防止中间计算溢出

using namespace std;

const int maxn = 2e5 + 5;
int A[maxn], B[maxn], C[maxn], n, m;

void solve()
{
    cin >> n >> m;
    // 读入原始数组 A
    for (int i = 1; i <= n; i++)
        cin >> A[i];

    // 步骤1:计算每一对对称位置变成相同所需的最小"向前增加量"
    // 操作是区间每个元素 +1 (mod m),所以 B[i] 表示 A[i] 要变成 A[n-i+1] 需要整体加多少 (mod m)
    // 这里计算 (A[i] - A[n-i+1] + m) % m 是因为我们只关心"加多少次"而不是"减多少次"
    // 因为操作只能增加 (mod m)
    for (int i = 1; i <= n / 2; i++)
        B[i] = (A[i] - A[n - i + 1] + m) % m;

    // 步骤2:差分,为了把"区间加"操作转化成"单点加"的约束
    // C[i] = B[i] - B[i-1] (mod m)
    // 这样,对原数组的区间加操作,在差分数组上表现为修改两个端点
    int S = 0;
    for (int i = 1; i <= n / 2; i++)
        C[i] = (B[i] - B[i - 1] + m) % m, S += C[i];

    // 步骤3:闭合成环
    // 因为差分数组的总和必须为 0 (mod m),这里补上最后一个差值,让总和是 m 的倍数
    C[n / 2 + 1] = (m - B[n / 2]) % m, S += C[n / 2 + 1];

    // 步骤4:排序,贪心选择
    // 我们可以让某些 C[i] 增加 m,这样整体操作次数不变(因为模 m 意义下相同),但可以减少"实际加法次数"
    // 因为操作是整体区间 +1,相当于我们可以"跳过"某些整圈的加法
    // S / m 表示有多少个整圈的 m 可以被"吸收"
    sort(C + 1, C + n / 2 + 2);

    // k 是我们必须保留的最小操作次数(即最后答案中会累加的最小几个 C[i])
    int k = n / 2 + 1 - S / m;

    int ans = 0;
    for (int i = 1; i <= k; i++)
        ans += C[i];

    cout << ans << '\n';
}

signed main()
{
    int T;
    cin >> T;
    while (T--)
    {
        solve();
    }
    return 0;
}

G - Mode in the Subtree

问题描述

给定一棵有根树,包含 (N) 个顶点,编号为 (1) 到 (N)。顶点 (1) 是根,顶点 (i) 的父节点是 pi(满足pi<i)p_i (满足 p_i < i)pi(满足pi<i)。

每个顶点都有颜色:顶点 (i) 的颜色为 ci(1≤ci≤N)c_i(1 \le c_i \le N)ci(1≤ci≤N)。

对于每个 v=1,2,...,Nv = 1, 2, \dots, Nv=1,2,...,N,解决以下问题:

设 fif_ifi 表示以顶点 vvv 为根的子树中,颜色为 iii 的顶点个数。

求:

  • 序列 (f1,f2,...,fN)(f_1, f_2, \dots, f_N)(f1,f2,...,fN) 中的最大值 mmm,以及
  • 满足 fi=mf_i = mfi=m 的正整数 i(i≤N)i(i \le N)i(i≤N)的个数 kkk。

解题思路

树上启发式合并,板子题不想多说了,维护每个颜色出现次数,出现次数为 ...\dots... 的颜色有多少种,当前最大出现次数。

代码

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

const int maxn = 2.5e6 + 5;   // 最大节点数

int N, M, F;
unsigned int state;           // 随机数生成器的状态
int Q[maxn], D[maxn];         // 存储输入的前 M 个父节点和颜色

vector<int> g[maxn];          // 邻接表,存树的孩子
int C[maxn];                  // 每个节点的颜色(0-based)

int sub[maxn];                // 子树大小
int euler[maxn], down[maxn], up[maxn], idx;  // DFS序相关
int stk[maxn * 2], top;       // 非递归DFS用的栈

int cnt[maxn], freq[maxn], mx; // cnt[col]=颜色出现次数, freq[x]=出现次数为x的颜色数, mx=当前最大出现次数
ll ans;                        // 最终答案(取模)

// 预处理子树大小,并将重儿子放在 g[c][0] 位置(用于启发式合并)
void dfs_size(int c)
{
    sub[c] = 1;
    for (int i = 0; i < (int)g[c].size(); i++)
    {
        int d = g[c][i];
        dfs_size(d);
        sub[c] += sub[d];
        // 重儿子:子树最大的儿子,交换到第一个位置
        if (sub[d] > sub[g[c][0]])
            swap(g[c][i], g[c][0]);
    }
}

// 非递归计算欧拉序,同时得到每个子树的区间 [down[c], up[c])
void dfs_order()
{
    idx = 0; top = 0;
    stk[top++] = ~0;  // 哨兵:负数表示子树结束标记
    stk[top++] = 0;   // 从根节点0开始
    while (top)
    {
        int c = stk[--top];
        if (c >= 0)   // 进入节点
        {
            euler[idx] = c;
            down[c] = idx++;
            // 孩子倒序入栈,保证处理顺序
            for (int i = (int)g[c].size() - 1; i >= 0; i--)
            {
                stk[top++] = ~g[c][i]; // 结束标记
                stk[top++] = g[c][i];  // 孩子节点
            }
        }
        else          // 离开节点
        {
            up[~c] = idx;  // 子树区间右端点(开区间)
        }
    }
}

// 添加节点 c 的颜色到当前统计中
void add(int c)
{
    int col = C[c];
    freq[cnt[col]]--;      // 原出现次数的颜色数减1
    cnt[col]++;            // 该颜色出现次数+1
    freq[cnt[col]]++;      // 新出现次数的颜色数加1
    if (cnt[col] > mx)     // 更新最大值
        mx = cnt[col];
}

// 对节点 c 的子树进行查询,并累加到 ans
void query(int c)
{
    ll a = mx ^ (c + 1);          // 题目要求的变换1
    ll b = freq[mx] ^ (c + 1);    // 题目要求的变换2
    ans = (ans + a * b) % 998244353;
}

// 清空节点 c 的子树信息(用于轻儿子)
void reset(int c)
{
    for (int i = down[c]; i < up[c]; i++)
    {
        int col = C[euler[i]];
        cnt[col] = 0;
    }
    for (int i = 0; i <= mx; i++)
        freq[i] = 0;
    mx = 0;
}

// DSU on tree(树上启发式合并)
void dsu(int c)
{
    // 先处理所有轻儿子(不保留信息)
    for (int i = 1; i < (int)g[c].size(); i++)
    {
        dsu(g[c][i]);
        reset(g[c][i]);   // 清空轻儿子的统计
    }
    // 处理重儿子(保留信息)
    if (!g[c].empty())
    {
        dsu(g[c][0]);     // 重儿子递归
        // 把重儿子子树中除了重儿子本身以外的节点加进来
        for (int i = up[g[c][0]]; i < up[c]; i++)
            add(euler[i]);
    }
    add(c);               // 加上当前节点
    query(c);             // 计算当前子树的答案
}

int main()
{
    scanf("%d%u%d%d", &N, &state, &M, &F);

    // 读取前 M 个父节点信息
    for (int i = 2; i <= M; i++) scanf("%d", &Q[i]);
    // 读取前 M 个颜色信息
    for (int i = 1; i <= M; i++) scanf("%d", &D[i]);

    // 构建树(节点编号从0开始)
    for (int i = 2; i <= N; i++)
    {
        int p;
        if (i <= M)
            p = Q[i] - 1;               // 直接使用输入
        else
        {
            p = state % (i - 1);        // 随机生成父节点
            state = (state * 1103515245u + 12345u) % 2147483648u;
        }
        g[p].push_back(i - 1);          // 添加边 p -> i-1
    }

    // 给节点赋值颜色(0-based)
    for (int i = 1; i <= N; i++)
    {
        if (i <= M)
            C[i - 1] = D[i] - 1;
        else
        {
            C[i - 1] = state % F;
            state = (state * 1103515245u + 12345u) % 2147483648u;
        }
    }

    dfs_size(0);   // 计算子树大小,找重儿子
    dfs_order();   // 计算欧拉序
    dsu(0);        // 开始 DSU on tree

    printf("%lld\n", ans);
    return 0;
}
相关推荐
钮钴禄·爱因斯晨2 小时前
聚焦操作系统中的PV操作
数据库·算法·系统架构·c#
云泽8082 小时前
笔试算法 - 双指针篇(一):移动零、复写零、快乐数与盛水容器
c++·算法
不才小强2 小时前
目标跟踪算法DeepSort实战
人工智能·算法·目标跟踪
papership2 小时前
【入门级-数学与其他:1.数及其运算:进制与进制转换:二进制、八进制、十进制、十六进制】
算法
ComputerInBook2 小时前
数字图像处理(4版)——第 4 章——频域滤波(下)(Rafael C.Gonzalez&Richard E. Woods)
人工智能·算法·计算机视觉·频域滤波
会编程的土豆3 小时前
【复习】二分查找
数据结构·c++·算法
Yzzz-F3 小时前
Problem - D - Codeforces
算法
chas_883 小时前
macbook air M5 32G本地跑ddtree-mlx效果
算法
programhelp_3 小时前
WeRide OA 2026 高频真题分享 & 详细备战指南
经验分享·算法·面试·职场和发展