比赛链接

A

题意

判断输入字符串与 \(\pi\) 的最长前缀匹配,不超过 \(30\) 位。

题解

知识点:模拟。

抄样例最后一个 \(30\) 都正确的,直接匹配。

时间复杂度 \(O(1)\)

空间复杂度 \(O(1)\)

代码

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

bool solve() {
    string s;
    cin >> s;
    string pi = "314159265358979323846264338327";
    int cnt = 0;
    for (int i = 0;i < s.size();i++) {
        if (s[i] != pi[i]) break;
        cnt++;
    }
    cout << cnt << '\n';
    return true;
}

int main() {
    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int t = 1;
    cin >> t;
    while (t--) {
        if (!solve()) cout << -1 << '\n';
    }
    return 0;
}

B

题意

\(n\) 个骰子,数字 \(a_i \in [1,6]\) 的面朝上,现在给你骰子面朝上数字的总和 \(s\) ,以及去掉一个最大值的数字总和 \(r\) ,要求还原一个合法的 \(a_i\)

题解

知识点:枚举。

方法有很多,这里提供一种写起来很方便的。

先存一个最大值 \(mx = s - r\) ,依次给每个骰子分配点数 \(\min(mx,r)\) ,超过最大值直接给最大值。

但是为了保证不出现数字为 \(0\) 的情况,一开始先给所有骰子分配 \(1\) 点。

时间复杂度 \(O(n)\)

空间复杂度 \(O(1)\)

代码

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

bool solve() {
    int n, s, r;
    cin >> n >> s >> r;
    s -= n;
    r -= n - 1;
    int mx = s - r;
    cout << mx + 1 << ' ';
    for (int i = 2;i <= n;i++) {
        cout << min(r, mx) + 1 << ' ';
        r -= min(r, mx);
    }
    cout << '\n';
    return true;
}

int main() {
    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int t = 1;
    cin >> t;
    while (t--) {
        if (!solve()) cout << -1 << '\n';
    }
    return 0;
}

C

题意

对一个排列 \(p\) ,生成 \(n\) 个长为 \(n-1\) 新序列,第 \(i\) 个序列由 \(p\) 除了 \(p_i\) 的元素构成。

现在给你 \(p\) 生成的 \(n\) 个序列,但顺序是打乱的,要求还原一个合法的 \(p\)

题解

知识点:构造。

注意到 \(p_1\)\(n\) 个序列的第一位会出现 \(n-1>1\) 次,而 \(p_2\) 只会出现 \(1\) 次 ,我们可以直接确定 \(p_1\) 。确定了 \(p_1\) ,我们找到没有 \(p_1\) 的一个序列,里面包含了 \(p_2,\cdots,p_n\) 直接输出即可。

时间复杂度 \(O(n^2)\)

空间复杂度 \(O(n^2)\)

代码

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

int a[107][107];
int cnt[107];
bool solve() {
    int n;
    cin >> n;
    int fst = 0;
    vector<int> cnt(n + 1);
    for (int i = 1;i <= n;i++) {
        for (int j = 1;j <= n - 1;j++) {
            cin >> a[i][j];
        }
        cnt[a[i][1]]++;
        if (cnt[a[i][1]] > 1) fst = a[i][1];
    }
    cout << fst << ' ';
    for (int i = 1;i <= n;i++) {
        if (a[i][1] != fst) {
            for (int j = 1;j <= n - 1;j++) cout << a[i][j] << " \n"[j == n - 1];
            break;
        }
    }
    return true;
}

int main() {
    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int t = 1;
    cin >> t;
    while (t--) {
        if (!solve()) cout << -1 << '\n';
    }
    return 0;
}

D

题意

给定一组数 \(a_i\) ,要求分出最少的分组,每组要求由连续上升的正整数构成。

对于连续上升,如 \(3,4,5,6\) 是连续上升的,而 \(1,3,4,5\) 不是,因为 \(1,3\) 中间跳过了 \(2\)

题解

知识点:贪心,枚举。

map 记录每个数的个数,从小到大遍历,设当前数字为 \(x\) ,上一次的数字为 \(pre\)\(cnt\) 表示数字的个数:

  1. 如果 \(x > pre + 1\) ,我们就必须开新的 \(cnt_x\) 个分组,答案加 \(cnt_x\)
  2. 如果 \(x = pre + 1\) ,则可以与上一次共用分组 \(cnt_x\) 个分组,但如果 \(cnt_x>cnt_{pre}\) ,那么多出来的部分要开新的分组,答案加 \(cnt_x-cnt_{pre}\)

时间复杂度 \(O(n \log n)\)

空间复杂度 \(O(n)\)

代码

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

bool solve() {
    int n;
    cin >> n;
    map<int, int> mp;
    for (int i = 1;i <= n;i++) {
        int x;
        cin >> x;
        mp[x]++;
    }
    int ans = 0, pre = -1;
    for (auto [x, y] : mp) {
        if (x > pre + 1) ans += y;
        else ans += max(0, y - mp[pre]);
        pre = x;
    }
    cout << ans << '\n';
    return true;
}

int main() {
    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int t = 1;
    cin >> t;
    while (t--) {
        if (!solve()) cout << -1 << '\n';
    }
    return 0;
}

E

题意

给定一个正整数 \(x \leq 2^{29}\) ,找到两个正整数 \(a,b \leq 2^{32}\) ,满足 \(\dfrac{a+b}{2} = a \oplus b = x\)

题解

知识点:位运算,构造。

我们分析 \(x\) 的第 \(i\)\(x_i = 1\) ,则 \(a,b\) 相关位置的情况,假设 \(a,b\) 一开始都为 \(0\)

  1. 首先满足 \(a \oplus b = x\) ,显然 \(a_i,b_i\) 必须是一个 \(1\) 和一个 \(0\) ,我们不妨设 \(a_i = 1,b_i = 0\)

  2. 其次要满足 \(\dfrac{a+b}{2} = x\) ,根据1得到的 \(a_i+b_i = 1\) 不能满足除以 \(2\) 使得 \(x_i = 1\) ,考虑对附近其他位做调整。

    考虑调整 \(i+1\) 位,那么 \(a_{i+1},b_{i+1}\) 必须是一个 \(1\) 和一个 \(0\) ,我们不妨设 \(a_{i+1} = 1,b_{i+1} = 0\) 。但是,这样 \(x_{i+1}\) 必须等于 \(1\) ,假设 \(x_{i+1}=1\),那么对于 \(i+1\) 位的第二步因为第 \(i\) 位锁定了,只能再往 \(i+2\) 位考虑,这是没有尽头的,所以这条路走不通。

    考虑调整 \(i-1\) 位,那么就必须 \(a_{i-1} = b_{i-1} = 1\) 才能产生一个进位使得 \(x_i = 1\) 。此时, \(x_{i-1}\) 必须等于 \(0\) ,假设 \(x_{i-1} = 0\) 就恰好满足所有需求。

综上,对于任何一个 \(x_i = 1\) 要满足 \(x_{i-1} = 0\) 时才有解(特别地, \(x_1 = 1\) 时无解)。满足有解条件后,我们考虑令 \(a_i = 1,b_i = 0\)\(a_{i-1} = b_{i-1} = 1\) ,即 \(a = 3 \cdot \dfrac{x}{2},b = \dfrac{x}{2}\)

时间复杂度 \(O(1)\)

空间复杂度 \(O(1)\)

代码

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

bool solve() {
    int x;
    cin >> x;
    int y = x >> 1;
    if ((x & 1) || (x & y)) return false;
    else cout << (x | y) << ' ' << y << '\n';
    return true;
}

int main() {
    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int t = 1;
    cin >> t;
    while (t--) {
        if (!solve()) cout << -1 << '\n';
    }
    return 0;
}

F

题意

题解

方法一

知识点:根号分治,bfs,贪心。

做法很简单,每加入一个黑点就bfs一遍附近找到最近的距离,主要在两个优化上:

  1. \(f_u\) 记录距离 \(u\) 最近的黑点的距离,每次只更新 \(f_v > f_u+1\) 的情况,能有效减小bfs的范围。
  2. \(ans\) 答案来限制搜索深度,因为距离大于 \(ans\) 的点没必要更新了,下一次更新 \(ans\) 一定在 \(f_u \leq ans-1\) 的情况。

证明:

在前 \(\sqrt n\) 次,一定能使得 \(ans\) 小于等于 \(2\sqrt n\) ,期间最多遍历 \(n\sqrt n\) 次。

在这之后,优化1能保证每个点只会在 \(f\) 变小时被遍历,优化2保证更新的距离不超过 \(ans \leq 2\sqrt n\) 。综上,每个点的 \(f\) 最多只会被更新 \(2\sqrt n\) 次,即每个点遍历不会超过 \(2 \sqrt n\) 次。

综上复杂度是 \(O(n\sqrt n)\)

时间复杂度 \(O(n \sqrt n)\)

空间复杂度 \(O(n)\)

方法二

知识点:根号分治,dfs,贪心。

不同于方法一的bfs遍历,这种方法设 \(f_u\) 为以 \(u\) 为根的子树中到 \(u\) 最近的黑点的距离,这样每次只需要更新 \(u\) 到根节点 \(1\) 路径上的点即可,并且保证更新层数不超过当前的 \(ans\) ,就可以做到和方法一一样的复杂度,但实际上跑的更快。

证明同方法一类似,前 \(\sqrt n\) 次能让 \(ans \leq 2\sqrt n\) ,后续每次遍历不超过 \(ans \leq 2\sqrt n\) 个点。

时间复杂度 \(O(n\sqrt n)\)

空间复杂度 \(O(n)\)

代码

方法一

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

struct Graph {
    struct edge {
        int v, nxt;
    };
    int idx;
    vector<int> h;
    vector<edge> e;

    Graph(int n, int m):idx(0), h(n + 1), e(m + 1) {}
    void init(int n) {
        idx = 0;
        h.assign(n + 1, 0);
    }

    void add(int u, int v) {
        e[++idx] = { v,h[u] };
        h[u] = idx;
    }
};
const int N = 200007, M = N << 1;
int c[N];
Graph g(N, M);
int f[N];

bool solve() {
    int n;
    cin >> n >> c[1];
    for (int i = 2;i <= n;i++) cin >> c[i];
    g.init(n);
    for (int i = 1;i < n;i++) {
        int u, v;
        cin >> u >> v;
        g.add(u, v);
        g.add(v, u);
    }

    for (int i = 1;i <= n;i++) f[i] = n + 1;
    int ans = n + 1;
    for (int i = 1;i <= n;i++) {
        ans = min(ans, f[c[i]]);
        f[c[i]] = 0;
        queue<int> q;
        q.push(c[i]);
        while (!q.empty()) {
            int u = q.front();
            q.pop();
            if (f[u] >= ans - 1) continue;
            for (int j = g.h[u];j;j = g.e[j].nxt) {
                int v = g.e[j].v;
                if (f[v] > f[u] + 1) {
                    f[v] = f[u] + 1;
                    q.push(v);
                }
            }
        }
        if (i > 1) cout << ans << " \n"[i == n];
    }
    return true;
}

int main() {
    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int t = 1;
    cin >> t;
    while (t--) {
        if (!solve()) cout << -1 << '\n';
    }
    return 0;
}

方法二

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

struct Graph {
    struct edge {
        int v, nxt;
    };
    int idx;
    vector<int> h;
    vector<edge> e;

    Graph(int n, int m):idx(0), h(n + 1), e(m + 1) {}
    void init(int n) {
        idx = 0;
        h.assign(n + 1, 0);
    }

    void add(int u, int v) {
        e[++idx] = { v,h[u] };
        h[u] = idx;
    }
};
const int N = 200007, M = N << 1;
int c[N];
Graph g(N, M);
int fa[N];
int f[N];

void dfs(int u, int fa) {
    ::fa[u] = fa;
    for (int i = g.h[u];i;i = g.e[i].nxt) {
        int v = g.e[i].v;
        if (v == fa) continue;
        dfs(v, u);
    }
}

bool solve() {
    int n;
    cin >> n >> c[1];
    for (int i = 2;i <= n;i++) cin >> c[i];
    g.init(n);
    for (int i = 1;i < n;i++) {
        int u, v;
        cin >> u >> v;
        g.add(u, v);
        g.add(v, u);
    }
    dfs(1, 0);

    for (int i = 1;i <= n;i++) f[i] = n + 1;
    int ans = n + 1;
    for (int i = 1;i <= n;i++) {
        int u = c[i], dis = 0;
        while (u && dis < ans) {
            ans = min(ans, dis + f[u]);
            f[u] = min(f[u], dis);
            dis++;
            u = fa[u];
        }
        if (i > 1)cout << ans << " \n"[i == n];
    }
    return true;
}

int main() {
    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int t = 1;
    cin >> t;
    while (t--) {
        if (!solve()) cout << -1 << '\n';
    }
    return 0;
}

G

题意

给一张 \(n\) 个点 \(m\) 条边的连通无向图,在图上的 \(b\) 个点会存在 bonus 标记(不可移动)。游戏开始前,会存在 \(p\)token 在图点上,可以按照以下步骤移动:

  1. 开局可以随意移动一个。
  2. 若某次 token 移动到了 bonus 点,那么可以有一次移动其他 token 机会。
  3. token 可以重叠。
  4. 某次移动使得 token 到编号为 \(1\) 的点,则游戏立刻胜利。

问游戏是否会胜利。

题解

知识点:模拟,贪心,枚举。

我们从两方面考虑:

  1. 能使 token 到达 \(1\) 的路径。
  2. token 能提供多少次移动机会。

先考虑何种路径才能到达 \(1\) 。为了保证每次移动都有下次机会,那么到 \(1\) 的路径除了起点和终点 \(1\) 都必须是 bonus 点。同时,我们还需要知道移动几次才能到达 \(1\) ,方便后面比较其他点的贡献。因此,我们从 \(1\) bfs,遇到不是 bonus 的点只更新距离,不放队列扩展。

再考虑 token 能提供多少机会。在此之前,我们对 bonus 点的性质进行讨论,发现如果 bonus 点相邻另一个 bonus 点,那么进入这个 bonus 点就能提供无限次机会,而剩下一些孤儿 bonus 点,则进入这个点只会提供一次机会,因此我们先对 bonus 点记录是否无限的状态,这个可以在建图的时候就处理好。之后,我们遍历所有 token ,如果他们的邻居是无限 bonus 点则可以提供无限次机会,这里可以记为 \(n\) 次;如果他们的邻居是孤儿 bonus 点则只能提供一次,记为 \(1\) 次。

最后我们求出 token 贡献总和,枚举每个 token 点判断能否到达 \(1\) 。如果可达,则减去这个点的贡献求出其他点的总贡献,如果其他点的贡献大于等于距离减 \(1\) (开局第一次移动不需要任何 bonus )则一定可以到达。

时间复杂度 \(O(n+m)\)

空间复杂度 \(O(n+m)\)

代码

#include <bits/stdc++.h>
using namespace std;
using ll = long long;


const int N = 200007;
vector<int> g[N];
int st[N], dis[N], val[N];

bool solve() {
    int n, m;
    cin >> n >> m;
    int p, b;
    cin >> p >> b;
    for (int u = 1;u <= n;u++) st[u] = 0, g[u].clear(), dis[u] = -1, val[u] = 0;
    for (int i = 1, x;i <= p;i++) cin >> x, st[x] |= 1;
    for (int i = 1, x;i <= b;i++) cin >> x, st[x] |= 2;
    for (int i = 1;i <= m;i++) {
        int u, v;
        cin >> u >> v;
        g[u].push_back(v);
        g[v].push_back(u);
        if ((st[u] & 2) && (st[v]) & 2) {
            st[u] |= 4;
            st[v] |= 4;
        }
    }
    dis[1] = 0;
    queue<int> q;
    q.push(1);
    while (!q.empty()) {
        int u = q.front();
        q.pop();
        for (auto v : g[u]) {
            if (~dis[v]) continue;
            dis[v] = dis[u] + 1;
            if (st[v] & 2) q.push(v);
        }
    }
    for (int u = 1;u <= n;u++) if (st[u] & 1) for (auto v : g[u]) if (st[v] & 2) { val[u] = st[v] & 4 ? n : 1;if (st[v] & 4) break; }
    ll sum = 0;
    for (int u = 1;u <= n;u++) if (st[u] & 1) sum += val[u];
    for (int u = 1;u <= n;u++) if (st[u] & 1) if (~dis[u] && sum - val[u] >= dis[u] - 1) { cout << "YES" << '\n'; return true; }
    return false;
}

int main() {
    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int t = 1;
    cin >> t;
    while (t--) {
        if (!solve()) cout << "NO" << '\n';
    }
    return 0;
}