Loading

[题解] 2022 CCPC 河南省赛 部分题解 2022 CCPC Henan Provincial Collegiate Programming Contest

正赛榜单:2022年河南省CCPC大学生程序设计竞赛 | RankLand

题目链接:Dashboard - 2022 CCPC Henan Provincial Collegiate Programming Contest - Codeforces

官方题解:CCPC Henan Provincial Contest 2022 Solution

A

签到,注意没有前导00到了 1 的后面。

Code

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

int n;
cin >> n;
if (n > 10) {
    cout << -1 << '\n';
} else {
    vector<int> t = {1, 0, 2, 3, 4, 5, 6, 7, 8, 9};
    for (int i = 0; i < n; i++) {
        cout << t[i];
    }
    cout << '\n';
}

B

首先转化,'a' = 1, 'e' = 2, 'h' = 3, 'n' = 4

注意到对哈希值求和之后是没有取模的,我们可以利用这点,想办法让分割出来的子串哈希值贴近 模数

最极端的情况,长度为 7 的串,第一位是 1,第二位<= 3,其余的串长度最大为 6。写了个 dp 没过。

注意到可以凹,即:超过了 7 位虽然会被模掉,但是也有可能 把哈希值凹的更高,但是如果超过 一个位数,一定是拆成更小的串更优,我们不妨尝试一下 20(实际上这个数在题解中证明了是 16)。

预处理整个串的哈希值,在 dp 的时候边转移边计算哈希。

dp 的时候注意到原字符串是个环,我们需要 转动几次这个串。

Code

设最长子串长度为 \(k\) ,时间复杂度 \(O(nk^2)\)。一个 k 是转动,一个 k 是转移。

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

const int maxn = 2e5 + 5;
const int mod = 998244353;
int n, k;
vector<int> a;

i64 work() {
    vector<i64> dp(n + 2, -1e18);
    dp[n + 1] = 0;
    for (int i = n; i >= 1; i--) {
        i64 cur = 0;
        for (int j = 1; i + j <= min(n + 1, i + k); j++) {
            cur = (cur * 31 + a[i + j - 1]) % mod;
            dp[i] = max(dp[i], dp[i + j] + cur); 
        }
    }

    return dp[1];
}

int main() {
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);

    string s;
    cin >> s;
    n = s.size();
    k = min(20, n);

    a.resize(n + 2);
    for (int i = 0; i < n; i++) {
        int t = 0;
        if (s[i] == 'a') t = 1;
        if (s[i] == 'e') t = 2;
        if (s[i] == 'h') t = 3;
        if (s[i] == 'n') t = 4;
        a[i + 1] = t;
    }

    i64 ans = 0;
    for (int i = 1; i <= k + 1; i++) {
        i64 res = work();
        ans = max(ans, res);
        vector<int> b = a;
        for (int j = 1; j < n; j++) {
            a[j] = b[j + 1];
        }
        a[n] = b[1];
    }
    cout << ans << '\n';

    return 0;
}

E

笔者读完题没想太多,写了一个很糖的 dp(01背包并记录下通过哪个字符转移).

一定有更简单的写法,这里网上有很多,不再赘述。

Code

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

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

int main() {
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);

    int n;
    cin >> n;
    string s;
    cin >> s;
    s = " " + s;
    vector<vector<int>> dp(3, vector<int>(26));
    vector<vector<char>> vp(3, vector<char>(26));
    for (int i = 1; i <= n; i++) {
        vector tp = dp;
        int x = s[i] - 'a';
        if (tp[2][x] >= 1) {
            dp[2][x]++;
        } else {
            for (int j = 0; j < 26; j++) {
                if (tp[1][j] >= 7) {
                    dp[2][x] = 1;
                    vp[2][x] = 'a' + j;
                    break;
                }
            }
        }
        if (dp[1][x] >= 1) {
            dp[1][x]++;
        } else {
            for (int j = 0; j < 26; j++) {
                if (tp[0][j] >= 5) {
                    dp[1][x] = 1;
                    vp[1][x] = 'a' + j;
                    break;
                }
            }
        }
        dp[0][x]++;
    }

    int ok = 0;
    vector<char> ans;
    for (int i = 0; i < 26; i++) {
        if (dp[2][i] >= 5) {
            ok = 1;
            ans.push_back('a' + i);
            ans.push_back(vp[2][i]);
            ans.push_back(vp[1][vp[2][i] - 'a']);
            break;
        }
    }

    if (!ok) {
        cout << "none" << '\n';
        return 0;
    }

    for (int i = 1; i <= 5; i++) {
        cout << ans[2];
    }
    for (int i = 1; i <= 7; i++) {
        cout << ans[1];
    }
    for (int i = 1; i <= 5; i++) {
        cout << ans[0];
    }
    cout << '\n';

    return 0;
}

F

注意到 \(A = \{ 0, 1, 2, 3, ..., k \}\ (k >= 0)\) 这种构造是可以构造出任意奇数的,因为 \(A + A= \{ 0, 1, 2, 3, ...,2k \}\) 的大小是 \(2k + 1\)

又因为 \(A = \{ 0, 2, 3, 4, 5,....,k \}\ (k >= 3)\) 这种构造,使得 \(1 \notin (A + A)\) 。其他数都是存在的,所以这样可以构造出 \(>= 6\) 的偶数,大小是 \(2k\)

\(2\)\(4\) 不能被构造出,因为 \(|A|=2\) 时,\(|A+A| = 3\)\(|A| = 3\) 时,\(5<=|A+A|<=6\)\(|A|\) 再大,答案就更大了。

Code

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

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

int main() {
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);

    int n;
    cin >> n;
    if (n == 2 || n == 4) {
        cout << -1 << '\n';
        return 0;
    }
    if (n % 2) {
        cout << n / 2 + 1 << '\n';
        for (int i = 0; i <= n / 2; i++) {
            cout << i << ' ';
        }
    } else {
        cout << n / 2 << '\n';
        cout << 0 << ' ';
        for (int i = 2; i <= n / 2; i++) {
            cout << i << ' ';
        }
    }

    return 0;
}

G

诈骗题,我以为题读假了,单开根本不敢写。中期队友读了也没问题才战战兢兢的交,过完之后人傻了。

根据题意,都是同一列的进行 & 运算,所以根本不影响最终的每一列的 & 运算。

直接把所有的字符串 & 之后输出即可。所有操作均无效。

Code

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

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

int main() {
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);

    int n, m;
    cin >> n >> m;
    vector<int> a(m, 1);
    for (int i = 1; i <= n; i++) {
        string s;
        cin >> s;
        for (int j = 0; j < m; j++) {
            a[j] &= s[j] - '0';
        }
    }
    int Q;
    cin >> Q;
    while (Q--) {
        int i, j, l, r, p;
        cin >> i >> j >> l >> r >> p;
    }
    cout << accumulate(a.begin(), a.end(), 0) << '\n';

    return 0;
}

H

这个题必须用 回溯 做,不回溯做不了。

不回溯 做不了的原因:

假设刚出起点遇到 L 型 水管,我们必须要做出一个 向左 或者 向右 的决策,如果不回溯,那么这个决策丢掉了,会走上错的路,对结果造成影响。

终点处出现 L 型水管同理。

值得注意的是,只有这 2 个地方能决策,剩下的地方 I 型水管 和 L 型水管都只有一个选择,这也是回溯能过的原因。

Code

回溯用 DFS 比较好实现。时间复杂度\(O(n)\)

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

int n, X, Y;
vector<vector<int>> vis;
vector<vector<char>> a;

// 0 上 | 1 右 | 2 下 | 3 左
bool dfs(int x, int y, int dir) {
    if (x == 4 && y == Y) {
        return 1;
    }
    if (x < 1 || x > 4 || y < 1 || y > n || vis[x][y] || a[x][y] == 0) {
        return 0;
    }
    vis[x][y] = 1;

    int ok = 0;

    if (a[x][y] == 'I') {
        if (dir == 0) {
            ok = dfs(x - 1, y, dir);
        } else if (dir == 1) {
            ok = dfs(x, y + 1, dir);
        } else if (dir == 2) {
            ok = dfs(x + 1, y, dir);
        } else {
            ok = dfs(x, y - 1, dir);
        }
    } else {
        if (dir == 0 || dir == 2) {
            ok = dfs(x, y + 1, 1) | dfs(x, y - 1, 3);
        } else {
            ok = dfs(x - 1, y, 0) | dfs(x + 1, y, 2);
        }
    }

    vis[x][y] = 0;

    return ok;
}

void solve() {
    cin >> n >> X >> Y;
    vis.assign(5, vector<int>(n + 1));
    a.assign(5, vector<char>(n + 1));
    for (int i = 1; i <= n; i++) {
        cin >> a[2][i];
    }
    for (int i = 1; i <= n; i++) {
        cin >> a[3][i];
    }
    a[1][X] = 'I';

    int ok = dfs(1, X, 2);

    cout << (ok ? "YES" : "NO") << '\n';
}

int main() {
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int tt = 1;
    cin >> tt;
    while (tt--) {
        solve();
    }
    return 0;
}

J

这种一次性求很多解的题,不是预处理,就是可以按顺序求。

我们可以考虑从小到大求,因为想要达到 \(mex = k\),我们必须要选择 所有 \(mex < k\) 的点,并且联通。我们就维护这个联通块,他是不断扩大的。

我们操纵 \(mex = 0\) 的点为根,这样可以保证联通块始终在树顶部。

可以用一个vis[]记录每个点是否在块里面。求解到 \(mex = i\ \ (1 <= i < n)\) 的点,设为x

  1. vis[x] == 1,说明这个点已经再块里。这表明不选x的情况下,无法选择 所有 \(i\) 的点,不满足题意,答案是-1
  2. vis[x] == 0,直接往树根跳,并且把路上的点tvis[t] = 1。并入联通块。答案就是n - sz[x]sz[x]x子树(含自身)的大小。

特殊两种情况,\(mex = 0\) 的答案,是最大子树的大小。\(mex = n\) 的答案,是整棵树。

Code

时间复杂度 \(O(n)\)。一直寻址,常数不小。跑了 \(1124ms\)

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

const int maxn = 1e6 + 6;
vector<int> g[maxn];
int sz[maxn], f[maxn];

void dfs(int x, int fa) {
    f[x] = fa;
    sz[x] = 1;
    for (auto &v : g[x]) {
        if (v == fa) {
            continue;
        }
        dfs(v, x);
        sz[x] += sz[v];
    }
}

int main() {
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int n;
    cin >> n;
    vector<int> a(n);
    for (int i = 1; i <= n; i++) {
        int x;
        cin >> x;
        a[x] = i;
    }

    for (int i = 2; i <= n; i++) {
        int x;
        cin >> x;
        g[i].push_back(x);
        g[x].push_back(i);
    }

    dfs(a[0], 0);

    vector<int> vis(n + 1);
    int cur = a[0];
    vis[cur] = 1;
    int res = 0;
    // 第一个答案是 mex=0 节点的最大子树大小。
    for (auto &v : g[cur]) {
        res = max(res, sz[v]);
    }
    cout << max(res, n - sz[cur]) << ' ';
    for (int i = 1; i < n; i++) {
        int x = a[i];
        if (vis[x]) {
            cout << 0 << ' ';
        } else {
            int t = x;
            while (!vis[t]) {
                vis[t] = 1;
                t = f[t];
            }
            cout << n - sz[x] << ' ';
        }
    }
    cout << n << '\n';

    return 0;
}

Bonus

赛时非常的糖,忘记把 \(mex = 0\) 的节点作为树根,导致联通块不一定在树顶。

写了个超级分类讨论,过了。代码:

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

const int maxn = 1e6 + 6;
vector<int> g[maxn];
int dep[maxn], sz[maxn], f[maxn];

void dfs(int x, int fa, int d) {
    f[x] = fa;
    dep[x] = d;
    sz[x] = 1;
    for (auto &v : g[x]) {
        if (v == fa) {
            continue;
        }
        dfs(v, x, d + 1);
        sz[x] += sz[v];
    }
}

int main() {
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);

    int n;
    cin >> n;
    vector<int> a(n);
    for (int i = 1; i <= n; i++) {
        int x;
        cin >> x;
        a[x] = i;
    }

    for (int i = 2; i <= n; i++) {
        int x;
        cin >> x;
        g[i].push_back(x);
        g[x].push_back(i);
    }

    dfs(1, 0, 1);

    vector<int> vis(n + 1);
    int cur = a[0];
    vis[cur] = 1;
    int res = 0;
    for (auto &v : g[cur]) {
        if (v == f[cur]) {
            continue;
        }
        res = max(res, sz[v]);
    }
    cout << max(res, n - sz[cur]) << ' ';
    for (int i = 1; i < n; i++) {
        if (vis[a[i]]) {
            cout << 0 << ' ';
            continue;
        }
        int t = a[i];
        int ok1 = 0;
        int ok2 = 0;
        while (dep[cur] != dep[t]) {
            if (dep[cur] > dep[t]) {
                vis[cur] = 1;
                cur = f[cur];
            } else {
                ok2 = 1;
                vis[t] = 1;
                t = f[t];
            }
            if (vis[t]) {
                cout << n - sz[a[i]] << ' ';
                ok1 = 1;
                break;
            }
        }
        vis[t] = 1;
        if (ok1) {
            continue;
        }
        if (!ok2 && cur == t) {
            // cout << sz[a[i]] - 1 << ' ';
            for (auto &v : g[cur]) {
                if (v == f[cur]) {
                    continue;
                }
                // res = max(res, sz[v]);
                if (vis[v]) {
                    res = sz[v];
                }
            }
            cout << res << ' ';
            continue;
        }
        while (cur != t) {
            vis[cur] = vis[t] = 1;
            t = f[t];
            cur = f[cur];
        }
        vis[cur] = 1;
        cout << n - sz[a[i]] << ' ';
    }
    cout << n << '\n';

    return 0;
}
posted @ 2025-05-17 21:26  Music163  阅读(250)  评论(0)    收藏  举报