「题解」2025 郑州邀请赛

卜算子·唉唉

唉唉唉唉唉
唉唉唉唉唉
唉唉唉唉唉唉唉
唉唉唉唉唉

唉唉唉唉唉
唉唉唉唉唉
唉唉唉唉唉唉唉
唉唉唉唉唉

D - 2025

Description

判断 $y$ 是否是完全平方数,且 $y$ 各位数字之和是否是完全平方数。

Solution

首先开场就注意到了一道很签的 D 题。然后队友一发过了。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
    string s;
    cin >> s;
    i64 s1 = 0, s2 = 0;
    for (auto ch : s) {
        s1 += ch - '0';
        s2 = s2 * 10 + ch - '0';
    }
    auto check = [](i64 x) {
        int sx = sqrt(x);
        return sx * sx == x;
    };
    if (check(s1) and check(s2))
        cout << "Yes\n";
    else
        cout << "No\n";

题外话:听说 louisdlee 开这题的时候 WA 了一发。

J - Ring Trick

Description

给出一个字符串,判断其凯撒移位后串中最多能有多少个「洞」。

Solution

纯模拟,队友开完 D 我就上去写了。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
    int holes_cnt[26] = {1, 2, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0};
    string s;
    cin >> s;
    int ans = 0;
    for (int i = 0; i < 26; i++) {
        int cur = 0;
        for (auto ch : s) {
            cur += holes_cnt[(ch - 'A' + i) % 26];
        }
        ans = max(ans, cur);
    }
    cout << ans << "\n";

M - 川陀航空学院

Description

给出 $n$ 个节点和 $m$ 条无向边,求需要多少次添加或删除边使得这 $n$ 个节点能构成一颗无向树。

Solution

使用 Kruskal 算法求最小生成树,并记录每棵树,所含的节点个数 $cnt_i$,和树的个数 $size$。答案就是:

$$ size-1+m-\sum_{i=1}^{size}cnt_i-1 $$
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
    int n, m;
    cin >> n >> m;
    DSU dsu(n);
    for (int i = 0; i < m; i++) {
        int u, v;
        cin >> u >> v;
        u--, v--;
        dsu.merge(u, v);
    }
    set<int> st;
    i64 ans = 0;
    for (int i = 0; i < n; i++) {
        st.insert(dsu.find(i));
    }
    for (auto x : st) {
        ans += dsu.siz[x] - 1;
    }
    cout << st.size() + m - ans - 1 << "\n";

H - 树论函数

Description

有无穷个孤立的节点,每个节点编号为 $i(i\ge 1)$

定义函数 $f(n)=n \cdot (n+1)$,若 $f(n)=f(a)\cdot f(b)$,则由节点 $n$ 出发,分别向节点 $a,b$ 连接一条无向边。

求从节点 $s$ 出发,在 $[l,r]$ 中有多少节点可达。

Solution

这道题是我做的……

首先注意到 $1 \le s, l, r \le 10^9$,因此必然存在某种规律使得我们可以 $O(1)$ 地解决此问题。

然后打表找规律,使用 Python 程序:

1
2
3
4
5
6
7
8
f = [0]
for i in range(1, 10000):
    f.append(i * (i + 1))
for a in range(1, 100):
    for b in range(a, 100):
        for n in range(b, 100):
            if f[a] * f[b] == f[n]:
                print(a, b, n)

使用 Python 的好处是可以直接在 Vim 里面输入 !python3 % 来运行……

然后发现对于每一个 $i$ 都与 $i+1$ 联通,因此可以得出每一个点都可达。

答案就是 $r-l+1$

代码略

Proof of $\ \ \forall a, b\ (1 \le a,\ b = a+1),\ \exists n \ [f(n) = f(a)\cdot f(b)] $:

Let $f(x) = x(x+1)$, and suppose $b = a + 1$.

Then we have:

$$f(a) \cdot f(b) = a(a+1) \cdot (a+1)(a+2) = (a^2 + 2a)(a^2 + 2a + 1)$$

Let $n = a^2 + 2a$.

Then:

$$f(n) = n(n+1) = (a^2 + 2a)(a^2 + 2a + 1)$$

Hence,

$$f(a) \cdot f(b) = f(n)$$

$\blacksquare$

F - 幻形之路

唉唉唉唉唉唉唉,唉唉唉唉唉唉唉。

Description

给定一个 $n\times m$ 的二维迷宫,迷宫中有若干障碍(用 # 表示障碍,用 . 表示空地),可以至多使用一次操作让操作后连续 $k$ 步可以忽略障碍,最小化从 $(1,1)$ 到 $(n,m)$ 的 $k$ 值。

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
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
#include <bits/stdc++.h>
using namespace std;

using i8 = int8_t;

constexpr int inf = 1e9;

int dx[4] = {-1, 1, 0, 0};
int dy[4] = {0, 0, -1, 1};

void solve() {
    int n, m;
    cin >> n >> m;
    vector<string> grid(n);
    for (int i = 0; i < n; ++i) {
        cin >> grid[i];
    }
    auto vis_s = vector(n, vector<i8>(m));
    auto vis_t = vector(n, vector<i8>(m));
    auto check = [&](int x, int y) {
        return x >= 0 and x < n and y >= 0 and y < m;
    };
    queue<pair<int, int>> q;
    vis_s[0][0] = true;
    q.emplace(0, 0);
    while (not q.empty()) {
        auto [x, y] = q.front();
        q.pop();
        for (int i = 0; i < 4; ++i) {
            int nx = x + dx[i], ny = y + dy[i];
            if (check(nx, ny) and not vis_s[nx][ny] and grid[nx][ny] == '.') {
                vis_s[nx][ny] = true;
                q.emplace(nx, ny);
            }
        }
    }
    vis_t[n - 1][m - 1] = true;
    q.emplace(n - 1, m - 1);
    while (not q.empty()) {
        auto [x, y] = q.front();
        q.pop();
        for (int i = 0; i < 4; ++i) {
            int nx = x + dx[i], ny = y + dy[i];
            if (check(nx, ny) and not vis_t[nx][ny] and grid[nx][ny] == '.') {
                vis_t[nx][ny] = true;
                q.emplace(nx, ny);
            }
        }
    }
    auto dist_s = vector(n, vector<int>(m, inf));
    auto dist_t = vector(n, vector<int>(m, inf));
    queue<pair<int, int>> qs, qt;
    for (int i = 0; i < n; ++i) {
        for (int j = 0; j < m; ++j) {
            if (vis_s[i][j]) {
                dist_s[i][j] = 0;
                qs.emplace(i, j);
            }
            if (vis_t[i][j]) {
                dist_t[i][j] = 0;
                qt.emplace(i, j);
            }
        }
    }
    while (not qs.empty()) {
        auto [x, y] = qs.front();
        qs.pop();
        for (int i = 0; i < 4; ++i) {
            int nx = x + dx[i], ny = y + dy[i];
            if (check(nx, ny) and dist_s[nx][ny] > dist_s[x][y] + 1) {
                dist_s[nx][ny] = dist_s[x][y] + 1;
                qs.emplace(nx, ny);
            }
        }
    }
    while (!qt.empty()) {
        auto [x, y] = qt.front();
        qt.pop();
        for (int i = 0; i < 4; ++i) {
            int nx = x + dx[i], ny = y + dy[i];
            if (check(nx, ny) and dist_t[nx][ny] > dist_t[x][y] + 1) {
                dist_t[nx][ny] = dist_t[x][y] + 1;
                qt.emplace(nx, ny);
            }
        }
    }
    int ans = inf;
    for (int i = 0; i < n; ++i) {
        for (int j = 0; j < m; ++j) {
            ans = min(ans, dist_s[i][j] + dist_t[i][j] - 1);
        }
    }
    ans = max(ans, 0);
    cout << ans << "\n";
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

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

G - 直径与最大独立集

Description

构造一颗树使得其直径(最长的两点之间的简单路径的长度)与最大独立集的大小(最多的能选出的两两不相邻的点的个数)相等。

Solution

基本构造

我们首先注意到,可以构造一个割点,使得在移除该点后,图被恰好分割为若干个连通分量。其中,恰有 $A$ 个连通分量是孤立点(仅包含一个顶点),另有一个连通分量是一个包含 $B$ 个节点的链状结构(即一条简单路径)。

对于这样一个图,其最大独立集的大小为 $A + \lceil B/2 \rceil$,而其直径的长度为 $B + 1$。

数值构造

我们令 $A = \lceil (n-1)/3 \rceil$,则此时的 $B$ 为 $(n - 1) - A$。我们考察此时的情况:

  1. 当 $n-1\equiv0(\bmod 3)$ 时,$A=(n-1)/3$
  2. 当 $n-1\equiv1(\bmod 3)$ 时,$A=(n+1)/3$
  3. 当 $n-1\equiv2(\bmod 3)$ 时,$A=n/3$

逐项考察

第一种情况
$$ B=(n - 1) - (n - 1)/3=\frac{2(n-1)}{3} ,B\equiv 0(\bmod 2)$$

则我们可以得到,此时最大独立集大小为

$$ A+B/2=\frac{2(n-1)}{3}=B$$

同时考虑到,其直径的长度恒为 $B + 1$,因此最大独立集大小不等于其直径长度。

有没有办法呢?有的兄弟,有的。

我们把链最后的一个节点取下,然后连上链的第一个节点,独立集的个数变化为

$$A+1+\lceil (B-1)/2 \rceil=A+1+(B-2)/2=A+B/2$$

因此此操作不会导致独立集个数的变化,但是直径减少了 $1$,这样我们就使得等式成立了。

第二种情况
$$ B=(n-1)-(n+1)/3=\frac{2(n+1)}{3}-2, B\equiv0(\bmod2) $$

有最大独立集大小:

$$A+B/2=\frac{n+1}{3}+\frac{n+1}{3}-1=\frac{2(n+1)}{3}-2+1=B+1$$

因此等式成立。

第三种情况
$$ B=n-1-n/3=2n/3-1, B\equiv1(\bmod2) $$

最大独立集大小:

$$A+(B+1)/2=n/3+n/3=2n/3=B+1$$

等式成立


故当 $n-1\equiv0(\bmod3)$ 时,构造一个「 $A$ 个点连在 $1$ 节点上,剩下 $B-1$ 个节点构成一条与 $1$ 节点联通的链,最后一个节点 $n$ 连接到链上与 $1$ 节点相连」的树即可。

其他情况直接构造一个「 $A$ 个节点连在 $1$ 上,其余构成一条链并与 $1$ 相连」的树即可。

另外需要注意的是,对于 $n\le4$ 的情况,我们需要特判一下。

 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
47
48
49
#include <bits/stdc++.h>
using namespace std;

int main() {
    cin.tie(nullptr)->sync_with_stdio(false);
    int T;
    cin >> T;
    while (T--) {
        int n;
        cin >> n;
        if (n == 2) {
            cout << "1 2\n";
            continue;
        }
        if (n == 3) {
            cout << "1 2\n2 3\n";
            continue;
        }
        if (n == 4) {
            cout << "-1\n";
            continue;
        }
        int a = ((n - 1) + 2) / 3;
        int b = n - a - 1;
        if ((n - 1) % 3 == 0) {
            int cur = 2;
            for (int i = 0; i < a; i++) {
                cout << "1 " << cur++ << "\n";
            }
            int rem = cur;
            cout << "1 " << rem << '\n';
            for (int i = 1; i < b - 1; i++) {
                cout << cur << " " << ++cur << "\n";
            }
            cout << rem << " " << ++cur << "\n";
            continue;
        } else {
            int cur = 2;
            for (int i = 0; i < a; i++) {
                cout << 1 << " " << cur++ << "\n";
            }
            cout << "1 " << cur << "\n";
            for (int i = 1; i < b; i++) {
                cout << cur << " " << ++cur << "\n";
            }
        }
    }
    return 0;
}

E - 双生魔咒

Description

给定 $2n$ 个字符串 $s_i$,将 $s_i$ 分成数量相等的两部分后,最大化两两配对形成的 $n^2$ 种组合的最长公共前缀之和。

Solution

常识上来说,若要最大化这个值,我们需要尽量把拥有公共前缀的字符串分到两边。

因为需要计算大量的字符串的公共前缀,我们使用了一个字典树来维护这些信息。并用一个数组 val[] 来维护字典树的某个节点的次数。

我们可以发现这个最大的值就是「每一个节点划分成两部分,两部分数量的乘积」之和。

我们令 $n=a+b$,其中 $n$ 是节点的次数,$a$ 和 $b$ 是被划分的两部分的次数。我们要最大化 $ab$,根据基本不等式有 $ab\le(a+b)^2/4$,当 $a=b$ 取等号。

因此我们令 $a=b=n/2,n\equiv0(\bmod2)$ 或者 $a=(n-1)/2,b=(n+1)/2,n\equiv1(\bmod2)$ 即可。

 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
#include <bits/stdc++.h>
using namespace std;

constexpr int maxlen = 1e6;
int trie[maxlen][26], tot;
int val[maxlen];

int newNode() {
    tot++;
    fill(trie[tot], trie[tot] + 26, 0);
    val[tot] = 0;
    return tot;
}

void strInsert(const string& s) {
    int p = 1;
    for (auto ch : s) {
        int x = ch - 'a';
        if (!trie[p][x]) {
            trie[p][x] = newNode();
        }
        p = trie[p][x];
        val[p]++;
    }
}

int main() {
    cin.tie(nullptr)->sync_with_stdio(false);
    int n;
    cin >> n;
    tot = 0;
    newNode();
    for (int i = 0; i < 2 * n; i++) {
        string s;
        cin >> s;
        strInsert(s);
    }
    long long ans = 0;
    for (int i = 0; i < maxlen; i++) {
        ans += 1ll * (val[i] / 2) * (val[i] / 2 + val[i] % 2);
    }
    cout << ans << "\n";
    return 0;
}
赣ICP备2024042422号
使用 Hugo 构建
主题 StackJimmy 设计