「题解」2025 CCPC 长春邀请赛

补题链接

G - 加边

Description

给你一张 $n$ 个点 $m$ 条边的无向图。可能存在重边或自环。问最少加多少边可以使得所有点的度数都为偶数,允许添加重边和自环。

Solution

显然度数为奇数的点的个数是偶数个。所以我们只需要把度数为奇数的点两两相连即可。

关于此结论的证明

因为一条边连接两个点,因此度数之和等于边数的两倍。

则度数为奇数的点必然为偶数个,这样才能使得度数之和为偶数。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
    int n, m;
    cin >> n >> m;
    auto adj = vector(n + 1, vector<int>{});
    for (int i = 0; i < m; i++) { 
        int u, v;
        cin >> u >> v;
        adj[u].emplace_back(v);
        adj[v].emplace_back(u);
    }
    vector<int> node;
    for (int i = 1; i <= n; i++)  {
        if (adj[i].size() % 2)
            node.emplace_back(i);
    }
    cout << node.size() / 2 << endl;
    for (int i = 0; i < node.size(); i += 2) {
        cout << node[i] << " " << node[i + 1] << "\n";
    }

I - 王国——求策

Description

给定 $2n$ 个点,$1 \sim n$ 和 $n+1 \sim 2n$ 的节点之间两两相连。同时每一个节点 $i$ 有且仅有一个「犯冲」的节点 $a_i$,且 $a_i$ 两两不同。给定 $s$ 和 $t$,问是否存在一条路径能联通 $s$ 和 $t$,且路径中任意两个节点之间不犯冲。

Solution

一般地,对于 $n\ge3$ 的情况,总是存在一个节点 $i $ 使得 $i$ 不与 $s$ 犯冲且不与 $t$ 犯冲。因此我们只需要判断 $s$ 是否与 $t$ 犯冲即可。

特别的,当 $n=1$时,显然不可达,当 $n=2$ 时,同侧的节点也不可达。根据如上逻辑编写代码即可。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
    int T;
    cin >> T;
    while (T--) {
        int n, s, t;
        cin >> n >> s >> t;
        vector<int> a(2 * n + 1);
        for (int i = 1; i <= n; i++) {
            cin >> a[i];
            a[a[i]] = a[i];
        }
        if (s == t) {
            cout << "Yes\n";
            continue;
        }
        if (n == 2) {
            if (s > t)
                swap(s, t);
            if (s == 1 and t == 2 or s == 3 and t == 4) {
                cout << "No\n";
                continue;
            }
        }
        if (a[s] == t or a[t] == s)
            cout << "No\n";
        else
            cout << "Yes\n";
    }

F - 年少的誓约II

Description

给定初始时间 $t_0$ 和时间区间 $[t_1,t_2]$。求最早的时间 $t$ 使得表针从 $t_0$ 转到 $t_1$ 的角度之和最小。

Solution

👋😭👋时针一定要动🖐️😭🖐️

暴力枚举每一个时间点即可。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
    int T;
    cin >> T;
    auto in_range = [](auto v, auto l, auto r) { return l <= v and v <= r; };
    auto get_close = [](auto s, auto t) {
        if (s > t)
            swap(s, t);
        return min(t - s, s + 720 - t);
    };
    // 1 hours = 30d because 30 * 12 = 360
    // 1 minutes = 6d because 6 * 60 = 360
    // but the Delta is that 30 * (m / 60) = m / 2
    // so the h angle = 30 * h + m / 2
    // so the m angle = 6 * m
    // we times 2
    while (T--) {
        int x0, y0, x1, y1, x2, y2;
        cin >> x0 >> y0 >> x1 >> y1 >> x2 >> y2;
        int dis = 114514;
        int a0, a1;
        for (int h = 23; h >= 0; h--) {
            for (int m = 59; m >= 0; m--) {
                if (not in_range(60 * h + m, 60 * x1 + y1, 60 * x2 + y2))
                    continue;
                int cur = get_close(60 * x0 + y0, 60 * h + m) +
                          get_close(12 * y0, 12 * m);
                if (cur <= dis) {
                    dis = cur;
                    a0 = h, a1 = m;
                }
            }
        }
        cout << a0 << " " << a1 << "\n";
    }

K - 微信小游戏

Description

给定一个 $n\times m$ 的矩阵,每格颜色在 ${1,\dots,m}$,并有一个初始颜色为 0 的“额外方块”。允许随时将额外方块染成任意非零颜色(不计次数),然后执行以下一次 “Shift” 操作(计作 1 次):

  • 选一列 $j$,将该列最下面的颜色弹出到额外方块;其余格下移一格;把当前额外方块放到该列最上方。

要求通过最少次 Shift,使得每列都变成一个单一的非零颜色,且 $m$ 列的颜色两两不同。

Solution

在不重复操作的情况下,我们只能把一列染成其最开头的颜色或完全不同的颜色。记录每一种颜色在开头的最大连续次数,答案就是 $n\times m$ 减去各颜色出现在开头的连续次数之和。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
    int n, m;
    cin >> n >> m;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            cin >> c[i][j];
        }
    }
    int ans = 0;
    vector<int> co(m + 1);
    for (int i = 1; i <= m; i++) {
        int j;
        for (j = 1; j <= n; j++) {
            if (c[j][i] != c[1][i])
                break;
        }
        j--;
        co[c[1][i]] = max(co[c[1][i]], j);
    }
    for (int i = 1; i <= m; i++) {
        ans += n - co[i];
    }
    cout << ans << endl;

A - GD 终极节奏实验室

Description

给定数组 $a$,统计使得 $\min(a_l,a_{l+1},\dots,a_r)=\gcd(a_l,a_{l+1},\dots,a_r)$ 成立的区间 $[l, r]$ 的个数。

Solution

注意到若 $\min(a_l,a_{l+1},\dots,a_r)=\gcd(a_l,a_{l+1},\dots,a_r)$ 成立,记此区间的最小值为 $m$,最大公约数为 $p$,有 $m\equiv p$,可得 $\forall i(l \le i \le r),a_i\equiv0(\bmod p)$,即每个数都是最小值的倍数或等于最小值。

因此我们对于每一个 $a_i$,只需要找到最大的 $l$ 和 $r$,使得 $\forall i(l_i \le j \le r_i),a_j\equiv0(\bmod a_i),a_j\le a_i$ 成立即可。

此时的答案就是 $\sum_{i=0}^{n}(i-l+1)(r-i+1)$。

我们可以利用单调栈,用维护四个单调栈:

  • la[i] 表示 i 左侧第一个“小于等于 a[i]”的位置,下一个位置 la[i]+1 就是「a[i] 作为最小值」可以延伸到的最左范围。也就是说 i 的最小值限制实际给出了左侧候选: la[i] + 1
  • ra[i] 表示 i 右侧第一个「小于 a[i]」的位置,i 作为最小值的合法区间只能延伸到 ra[i]-1
  • lb[i] 表示 i 左侧第一个「a[j] % a[i] != 0」的位置,那么 i 要能整除左边所有元素,左边界就得从 lb[i]+1 开始。
  • rb[i] 表示 i 右侧第一个「a[j] % a[i] != 0」的位置,i 能整除右边所有元素,右边界就最多到 rb[i]-1

按此实现即可。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
    int n;
    cin >> n;
    vector<int> a(n), la(n), lb(n, -1), ra(n), rb(n, n);
    for (int i = 0; i < n; i++) {
        cin >> a[i];
    }

    stack<int> stk;
    for (int i = 0; i < n; i++) {
        while (not stk.empty() and a[stk.top()] > a[i])
            stk.pop();
        la[i] = stk.empty() ? -1 : stk.top();
        stk.push(i);
    }
    stk = {};
    for (int i = n - 1; i >= 0; i--) {
        while (not stk.empty() and a[stk.top()] >= a[i])
            stk.pop();
        ra[i] = stk.empty() ? n : stk.top();
        stk.push(i);
    }
    stk = {};
    for (int i = n - 1; i >= 0; i--) {
        while (not stk.empty() and a[i] % a[stk.top()] != 0) {
            lb[stk.top()] = i;
            stk.pop();
        }
        stk.push(i);
    }
    stk = {};
    for (int i = 0; i < n; i++) {
        while (not stk.empty() and a[i] % a[stk.top()] != 0) {
            rb[stk.top()] = i;
            stk.pop();
        }
        stk.push(i);
    }
    ll ans = 0;
    for (int i = 0; i < n; i++) {
        int l = max(la[i], lb[i]) + 1;
        int r = min(ra[i], rb[i]) - 1;
        if (l <= i and i <= r) {
            ans += 1ll * (i - l + 1) * (r - i + 1);
        }
    }
    cout << ans << endl;

H - 王国——迁移

Description

有 $n$ 组待搬迁的居民,第 $j$ 组有 $b_j$ 人,禁止搬入城市 $a_j$;保留的 $n$ 座城市中第 $i$ 座有“不满系数” $c_i$。每组居民必须分配到一个不冲突的城市上,若城市 $i$ 上最终搬入总人数为 $d_i$,它的“不满度”为 $c_i,d_i$。要求在所有满足“第 $j$ 组不去 $a_j$”的分配方案里,使得各城市中不满度的最大值最小,输出该最小值。

Solution

最小化最大值,显然使用二分答案法。

我们考虑 check 函数的内容,只需要判断在允许每座城市最多接纳 $\lceil x/c[i]\rceil\lceil x/c[i]\rceil$ 人的条件下,能否把所有组都分配到它们允许的城市里即可。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
void solve() {
    int n;
    cin >> n;
    vector<int> a(n + 1), b(n + 1), c(n + 1);
    for (int i = 1; i <= n; i++)
        cin >> a[i];
    for (int i = 1; i <= n; i++)
        cin >> b[i];
    for (int i = 1; i <= n; i++)
        cin >> c[i];
    auto check = [&](int x) {
        vector<int> d(n + 1);
        int sumb = 0, sumd = 0;
        for (int i = 1; i <= n; i++) {
            d[i] = x / c[i];
            sumb += b[i], sumd += d[i];
        }
        for (int i = 1; i <= n; i++) {
            if (b[i] > sumd - d[a[i]]) {
                return false;
            }
        }
        if (sumb > sumd)
            return false;
        return true;
    };
    int lo = 0, hi = 1e18, ans = -1;
    while (lo <= hi) {
        int mid = (lo + hi) >> 1;
        if (check(mid)) {
            ans = mid;
            hi = mid - 1;
        } else {
            lo = mid + 1;
        }
    }
    cout << ans << "\n";
}
Licensed under CC BY-NC-SA 4.0
最后更新于 Jul 20, 2025 19:32 +0800
赣ICP备2024042422号
使用 Hugo 构建
主题 StackJimmy 设计