2024 ICPC南京

The 2024 ICPC Asia Nanjing Regional Contest

外榜 https://board.xcpcio.com/icpc/49th/nanjing

铜线: 3题406-137 ~ 4题607

银线: 4题606-357 ~ 5题650

金线: 5题645-467 6题+

难度(个人感觉):

纯签: E

半签: J

Easy: K

Mid: GB

E

题意

一个字符串循环左移(第一个放到最后)最多\(k\)次,求包含子串"nanjing"的最大数量。

思路

水题,暴力即可。

代码

cpp 复制代码
void solve() {
    int n, k;
    cin >> n >> k;
    string s;
    cin >> s;
    auto find = [&] (string s) -> int {
        int ans = 0;
        for (int i = 6; i < n; i++) {
            string str = s.substr(i - 6, 7);
            if (str == "nanjing") {
                ans++;
            }
        }
        return ans;
    };
    int ans = find(s);
    for (int i = 0; i < std::min(7, k); i++) {
        char x = s[0];
        std::reverse(all(s));
        s.pop_back();
        std::reverse(all(s));
        s.push_back(x);
        ans = std::max(ans, find(s));
    }
    cout << ans << "\n";
}

J

题意

一共\(k\)个人,你与\(n\)个是好友。

有\(m\)条消息,每条消息和某两个人相关,如果你和这两个人都是好友,就能看到这条消息。

最多可以添加两个新好友,求能看到的最多的消息数量。

思路

要么选两个之间有消息的,要么直接选两个相关消息最多的。

代码

cpp 复制代码
void solve() {
    int n, m, k;
    cin >> n >> m >> k;
    vector<bool> a(k + 1);
    for (int i = 0; i < n; i++) {
        int x;
        cin >> x;
        a[x] = 1;
    }
    vector<int> cnt(k + 1);
    int ans = 0;
    std::map<std::pair<int, int>, int> mp;
    for (int i = 0; i < m; i++) {
        int x, y;
        cin >> x >> y;
        if (a[x] && a[y]) {
            ans++;
        } else if (a[x]) {
            cnt[y]++;
        } else if (a[y]) {
            cnt[x]++;
        } else {
            if (x == y) {
                cnt[x]++;
            } else if (x > y) {
                std::swap(x, y);
            }
            mp[{x, y}]++;
        }
    }
    int add = 0;
    for (auto [o, s] : mp) {
        auto [x, y] = o;
        add = std::max(add, s + (x != y) * (cnt[x] + cnt[y]));
    }

    std::sort(all(cnt), std::greater<>() );
    add = std::max(add, cnt[0] + cnt[1]);

    cout << ans + add << "\n";
}

K

题意

一共\(w \le 10^9\)个格子,其中\(n\)个红色,\(m\)个黑色,其他都是白色。

使用长度为\(k\)的带子覆盖所有红色,且不能覆盖到黑色,每个格子最多覆盖一次。

求需要的最少带子的数量,或者不存在方案。

思路

用黑色把w个格子分成若干个区间,在每个区间内去覆盖,带子不能超出区间。

对于每个区间,先以最左端的红色为起点,依次向右覆盖,最后一个超出区间,就需要向左移,和前一个重叠了,前一个也需要向左移,依此类推,如果最左端的也超出区间,则不存在方案。

算是个模拟吧。

代码

cpp 复制代码
void solve() {
    int n, m, k, w;
    cin >> n >> m >> k >> w;
    vector<int> a(n);
    for (int i = 0; i < n; i++) {
        cin >> a[i];
    }
    vector<int> b(m);
    for (int i = 0; i < m; i++) {
        cin >> b[i];
    }
    b.push_back(0);
    b.push_back(w + 1);

    std::sort(all(a));
    std::sort(all(b));
    std::vector<std::pair<int, int>> seg;
    for (int i = 1; i < b.size(); i++) {
        seg.push_back({b[i - 1] + 1, b[i] - 1});
    }

    int ans = 0;
    vector<int> res;
    for (auto [l, r] : seg) {
        int x = std::lower_bound(all(a), l) - a.begin();
        int y = std::lower_bound(all(a), r + 1) - a.begin() - 1;
        if (x >= n || x > y) {
            continue;
        }
        if (r - l + 1 < k) {
            cout << -1 << "\n";
            return;
        }
        vector<std::pair<int, int>> seg2;
        int tail;
        for (int i = x; i <= y; i++) {
            if (i == x) {
                tail = a[i] + k - 1;
                seg2.push_back({a[i], tail});
            } else if (a[i] > tail) {
                tail = a[i] + k - 1;
                seg2.push_back({a[i], tail});
            }
        }
        if (seg2.back().second > r) {
            auto &[L, R] = seg2.back();
            int del = R - r;
            L -= del, R -= del;

            for (int i = seg2.size() - 2; i >= 0; i--) {
                if (seg2[i].second >= seg2[i + 1].first) {
                    int del = seg2[i].second - seg2[i + 1].first + 1;
                    seg2[i].first -= del;
                    seg2[i].second -= del;
                }
            }
            if (seg2[0].first < l) {
                cout << -1 << "\n";
                return;
            }
        }
        for (auto [L, R] : seg2) {
            res.push_back(L);
        }
        ans += seg2.size();
    }
    cout << ans << "\n";
    for (auto i : res) {
        cout << i << " ";
    }
    cout << "\n";
}

G

题意

  • 交互

一棵\(n\)节点的二叉树,最多可以\(p\)次询问,\(2^p \le n\)。找出一个事先设定的特殊点。

cpp 复制代码
int query(int u, int v) { // 每次询问两个节点
    std::cout << "? " << u << " " << v << std::endl;
    int t;
    std::cin >> t;
    // t = 0 特殊点离 u 近
    // t = 1 特殊点与 u v 的距离相等
    // t = 2 特殊点离 v 近
    // 距离表示简单路径上的边数
    return t;
}

思路

\(log_2n\)次查询,明显二分。vp时写了2小时二分,写了一坨没做出来。

看官方题解发现,要以树的重心为分界点走分支。每次查询重心子树最大的两个子节点,离重心近则分离两个子树,否则分离除了离得近的子树的所有节点。

代码

cpp 复制代码
void solve() {
    int n;
    cin >> n;

    vector<vector<int>> e(n + 1);
    for (int i = 1; i <= n; i++) {
        int x, y;
        cin >> x >> y;
        if (x) {
            e[i].push_back(x);
            e[x].push_back(i);
        }
        if (y) {
            e[i].push_back(y);
            e[y].push_back(i);
        }
    }

    auto query = [&] (int u, int v) -> int {
        cout << "? " << u << " " << v << std::endl;
        int t;
        cin >> t;
        return t;
    };
    vector<bool> del(n + 1);
    vector<int> size(n + 1), max(n + 1);
    auto dfs = [&] (auto &&dfs, int root) -> void {
        int g;
        auto find = [&] (auto &&find, int x, int fa) -> void {
            size[x] = 1;
            max[x] = 0;
            for (auto u : e[x]) {
                if (u != fa && !del[u]) {
                    find(find, u, x);
                    size[x] += size[u];
                    max[x] = std::max(max[x], size[u]);
                }
            }
            max[x] = std::max(max[x], n - size[x]);
            if (max[x] <= n / 2) {
                g = x;
            }
        };
        find(find, root, 0);
        find(find, g, 0);

        vector<std::pair<int, int>> son;
        for (auto u : e[g]) {
            if (!del[u]) {
                son.push_back({size[u], u});
            }
        }
        std::sort(all(son), std::greater<> ());
        if (son.empty()) {
            cout << "! " << g << std::endl;
            return;
        }
        int u = g, v = 0;
        if (son.size() == 1) {
            v = son[0].second;
            int t = query(u, v);
            if (t == 0) {
                cout << "! " << u << std::endl;
            } else {
                cout << "! " << v << std::endl;
            }
        } else {
            u = son[0].second, v = son[1].second;
            int t = query(u, v);
            if (t == 0) {
                del[g] = 1;
                n = size[u];
                dfs(dfs, u);
            } else if (t == 2) {
                del[g] = 1;
                n = size[v];
                dfs(dfs, v);
            } else {
                del[u] = del[v] = 1;
                n -= size[u] + size[v];
                dfs(dfs, g);
            }
        }
    };

    dfs(dfs, 1);
}

B

题意

一个包含\(012\)三种字符的字符串,可以执行任意次操作,每次操作,可以将某个\(2\)改为\(0\)或\(1\),或者选择相邻的两个\(0\)或\(1\)删掉并拼接字符串。求字符串可以达到的最短长度。

思路

神秘题目。

若删除两个字符,一定是删除一个奇数位一个偶数位,且每个字符下标的奇偶性不会变。

将所有偶数位取反,那么删除操作就变成了删除相邻两个不同的,则最终字符串一定是全\(0\)或全\(1\)。

所以\(2\)就可以每次修改成当前数量少的那一个。

代码

cpp 复制代码
void solve() {
    string s;
    cin >> s;
    for (int i = 1; i < s.size(); i += 2) {
        if (s[i] == '0') {
            s[i] = '1';
        } else if (s[i] == '1') {
            s[i] = '0';
        }
    }
    int s0 = std::count(all(s), '0');
    int s1 = std::count(all(s), '1');
    int s2 = std::count(all(s), '2');

    if (s0 + s2 < s1) {
        cout << s1 - (s0 + s2) << "\n";
    } else if (s1 + s2 < s0) {
        cout << s0 - (s1 + s2) << "\n";
    } else {
        cout << ((s0 + s1 + s2) & 1) << "\n";
    }
}