Loading

[题解] 2025 ICPC 南昌邀请赛暨江西省赛 部分题解 2025 ICPC Nanchang Invitational and Jiangxi Provincial Collegiate Programming Contest

题目链接:Dashboard - 2025 ICPC Nanchang Invitational and Jiangxi Provincial Collegiate Programming Contest - Codeforces

官方题解:[codeforces.com/gym/105911/attachments/download/31650/2025 ICPC Nanchang Invitational Solution.pdf](https://codeforces.com/gym/105911/attachments/download/31650/2025 ICPC Nanchang Invitational Solution.pdf)

赛时榜单:2025年icpc全国邀请赛(南昌)暨2025年(icpc)江西省大学生程序设计竞赛 - 正式赛 | Board - XCPCIO

这把读题占模严重啊,英文题目且奇长。。

A

int a, b, c, d;
cin >> a >> b >> c >> d;
cout << (a + b + c) * d << '\n';

D

值域非常的大,我们直接离散化,然后差分一下,前缀和起来取 \(\max\) 就可以了。

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

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

    map<int, int> mp;
    int n, a, b, c;
    cin >> n >> a >> b >> c;
    mp[a] = mp[b] = mp[c] = 0;
    vector<array<int, 6>> in(n + 1);
    for (int i = 1; i <= n; i++) {
        for (auto &e : in[i]) {
            cin >> e;
        }
        auto &[x, y, z, X, Y, Z] = in[i];
        mp[x] = mp[y] = mp[z] = mp[X] = mp[Y] = mp[Z] = 0;
        if (x > X) swap(x, X);
        if (y > Y) swap(y, Y);
        if (z > Z) swap(z, Z);
    }

    int sz = 0;
    for (auto &[f, s] : mp) {
        s = ++sz;
    }
    vector<int> bx(sz + 2), by(sz + 2), bz(sz + 2);
    for (int i = 1; i <= n; i++) {
        auto &[x, y, z, X, Y, Z] = in[i];
        bx[mp[x]]++, bx[mp[X] + 1]--;
        by[mp[y]]++, by[mp[Y] + 1]--;
        bz[mp[z]]++, bz[mp[Z] + 1]--;
    }

    int ans = 0;
    for (int i = 1; i <= sz; i++) {
        bx[i] += bx[i - 1];
        by[i] += by[i - 1];
        bz[i] += bz[i - 1];
        ans = max({ans, bx[i], by[i], bz[i]});
    }
    cout << ans << '\n';

    return 0;
}

E

我们注意到给了 3 秒,范围是 3e5,对于题目这种,区间扩展收缩可以计算答案的题,考虑莫队。

如何计算 区间内的 k-fold 串个数呢?要想形成 k-fold 串,充要 是串内的所有字符都被 k 整除。

我们在 mod k 意义下计算一个前缀桶,然后对其进行 序号哈希,当序号[l - 1] == 序号[r]时,\(s_{l, r}\) 是 k-fold串。详见代码。

莫队,桶+哈希,\(O(n \sqrt {n} + n\log n)\)

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

int B, n, k, Q, idx;
i64 res;
vector<int> c;

struct qu {
    int l, r, i;
};

bool operator<(const qu &a, const qu &b) {
    if (a.l / B != b.l / B) {
        return a.l < b.l;
    } else {
        return a.r < b.r;
    }
}

inline void add(int x) {
    res += c[x];
    c[x]++;
}

inline void del(int x) {
    c[x]--;
    res -= c[x];
}

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

    idx = res = 0;
    cin >> n >> k >> Q;
    B = sqrtl(n);
    string s;
    cin >> s;
    s = " " + s;
    vector<int> a(26);
    map<vector<int>, int> mp;
    vector<int> has(n + 1);
    mp[a] = 0;
    for (int i = 1; i <= n; i++) {
        a[s[i] - 'a']++;
        a[s[i] - 'a'] %= k;
        if (!mp.count(a)) {
            mp[a] = ++idx;
        }
        has[i] = mp[a];
    }
    c.assign(idx + 1, 0);

    vector<qu> q(Q + 1);
    for (int i = 1; i <= Q; i++) {
        cin >> q[i].l >> q[i].r;
        q[i].l--;
        q[i].i = i;
    }
    sort(q.begin() + 1, q.end());

    vector<i64> ans(Q + 1);
    for (int i = 1, l = 1, r = 0; i <= Q; i++) {
        while (l > q[i].l) add(has[--l]);
        while (r < q[i].r) add(has[++r]);
        while (l < q[i].l) del(has[l++]);
        while (r > q[i].r) del(has[r--]);
        ans[q[i].i] = res;
    }

    for (int i = 1; i <= Q; i++) {
        cout << ans[i] << '\n';
    }

    return 0;
}

F

诈骗。

因为 \(c_i\) 始终是 \(c_{i - 1}\)\(r_{i - 1}\) 加权得来,而且权重都是一样的。而答案就是 \(\sum ^{n} _{i = 1}(c_i-r_i)\),所以尽可能调整 \(r_i\) 使得 新的 \(c_{i +1}\) 变得更少就行了。没必要减缓 \(c_i\) 的下降速度,越等损失的越多。

所以由我们操控的 \(r_i\) 每次都设为 \(L\)

void solve() {
    int n, k;
    cin >> n >> k;
    vector<ld> r(n + 1, 1), c(n + 1);
    ld p, L, R;
    cin >> r[0] >> c[0] >> p >> L >> R;
    for (int i = 1; i <= n; i++) {
        r[i] = L;
    }
 
    for (int i = 1; i <= k; i++) {
        int p;
        ld v;
        cin >> p >> v;
        r[p] = v;
    }
 
    ld ans = 0;
    for (int i = 1; i <= n; i++) {
        c[i] = p * c[i - 1] + (1 - p) * r[i - 1];
        ans += c[i] - r[i];
    }
 
    cout << fixed << setprecision(10);
    cout << ans << '\n';
 
}

G

可以发现 每次都是除的话,\(\log\) 次内就能除完(\(d >= 2\))。我们可以倒着 dp 预处理走哪条路最优。

具体地,我们可以设 \(dp[i][u]\) 代表 从 \(u\) 出发,走 \(i\) 条边最多能除多少。显然这个点的第 \(i\) 个状态从 指向的点的第 \(i-1\) 个状态转移。故有:

\[dp[i][u] = \max \{dp[i - 1][v] * w\} \]

其中 存在一条 从 \(u\)\(v\) 的边,权是 \(w\)

题目保证有一条出边,所以肯定能转移。

注意转移的时候是同时转移,所以我们可以再开个数组缓冲,详见代码。

时间复杂度:\(O(\ (m + Q) * \log(\max\{x_i\})\ )\)。每个点有一条出边,\(m >= n\)

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

const int maxn = 5e5 + 5;
vector<pair<int, int>> g[maxn];

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

    int n, m, Q;
    cin >> n >> m >> Q;
    for (int i = 1; i <= m; i++) {
        int u, v, w;
        cin >> u >> v >> w;
        g[u].emplace_back(v, w);
    }

    // 2 ^ 30 > 1e9
    vector<vector<i64>> dp(31, vector<i64>(n + 1, 1));
    for (int i = 1; i <= 30; i++) {
        vector<i64> tp(n + 1, 1);
        for (int u = 1; u <= n; u++) {
            if (dp[i - 1][u] >= 1e9) {
                continue;
            } 
            for (auto &[v, w] : g[u]) {
                tp[u] = max(tp[u], dp[i - 1][v] * w);
            }
        }
        dp[i] = tp;
    }

    while (Q--) {
        int p, x;
        cin >> p >> x;
        for (int i = 1; i <= 30; i++) {
            if (dp[i][p] > x) {
                cout << i << '\n';
                break;
            }
        }
    }

    return 0;
}

I

\(k\) 是确定的,这提示我们使用双指针。

首先我们可以对前 \(k\)1 任意组合。设 \(len\)\(k\)1 所占有的区间长度(包括左端和右端延伸出的0,形如001101000),则有 \(C^{k}_{len}\)

然后我们移动双指针,找到一个新的1,必须要放掉一个老的1。此时双指针确定的区间,自然是形如001101,左端是 多余的0,右端一定是1。但其实,我们可以把右端的0也包进来,所以提前预处理好每个1右边有多少个0,设为\(suf[r]\)

得到形如001101000。长度为 \(len = r - l +1 + suf[r]\),答案 加上 \(C^{k}_{len}\)

注意容斥。我们如果不动 r及其右边的0的话,约等于没选这个新的1。这种状态在之前已经包含过了,再计算肯定是不对的,所以我们要 减去这种情况,这种情况是 在 \([l, r - 1]\)中 写入 \(k - 1\)1

写的很丑。双指针,\(O(n)\)

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

const int mod = 998244353;
const int maxn = 1e5 + 5;

i64 f[maxn], g[maxn];
i64 ksm(i64 a, i64 n) {
    i64 ans = 1;
    a %= mod;
    while (n) {
        if (n & 1) {
            ans = ans * a % mod;
        }
        a = a * a % mod;
        n >>= 1;
    }
    return ans;
}

void init() {
    f[0] = 1;
    g[0] = 1;
    for (int i = 1; i < maxn; i++) {
        f[i] = f[i - 1] * i % mod;
        g[i] = g[i - 1] * ksm(i, mod - 2) % mod;
    }
}

inline i64 C(i64 n, i64 m) {
    if (m > n || n < 0 || m < 0) {
        return 0;
    }
    if (m == 0) {
        return 1;
    }
    return f[n] * g[m] % mod * g[n - m] % mod;
}

void solve() {
    int n, k;
    cin >> n >> k;
    string s;
    cin >> s;
    s = " " + s;
    int sz = 0;
    int fl = 0;
    i64 ans = 0;
    int cur = 0;
    vector<int> suf(n + 1);
    for (int i = n; i >= 1; i--) {
        cur += s[i] == '0';
        suf[i] = cur;
        if (s[i] == '1') {
            cur = 0;
        }
    }

    for (int l = 1, r = 1; r <= n; r++) {
        if (s[r] == '1') {
            sz++;
            while (l <= r && sz > k) {
                if (s[l] == '1') {
                    sz--;
                }
                l++;
            }

            if (sz == k) {
                ans = (ans + C(r - l + 1 + suf[r], k)) % mod;
                if (!fl) {
                    fl = 1;
                } else {
                    ans = (ans - C(r - l, k - 1) + mod) % mod;
                }
            }
        }
    }
    cout << ans << '\n';
}

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

K

不难发现 按住一个石像转 1 次 + 按住自己转 3 次 = 把一个石像往反方向转 1 次

另外操作二最多不超过 3 次,所以我们可以无限用,然后\(\mod 4\) 就可以了

最终肯定是把所有雕像都调成一致,再用操作二转回 前方。调成一致调成哪个最优,反正就 4 个方向,直接枚举。

#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;
    vector<int> a(n + 1);
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
    }

    vector<i64> b(4);
    for (int i = 1; i <= n; i++) {
        for (int j = 0; j <= 3; j++) {
            b[j] += (a[i] - j + 4) % 4;
        }
    }

    i64 ans = 1e18;
    for (int i = 0; i <= 3; i++) {
        ans = min(ans, b[i] + (b[i] * 3 + 4 - i) % 4);
    }
    cout << ans << '\n';

    return 0;
}

M

n个硬币面朝上,翻转其中k个。问能不能、怎么把硬币分成 正面朝上的相同的两堆

答案肯定是都能的:

我们直接分成 kn - k 大小的两堆。假设 第一堆中 有 x个被翻转了。

第一堆中k - x个正面朝上,第二堆中 n - k - (k - x) = n - 2 * k + x 个硬币正面朝上。

所以第二堆中 n - k - (n - 2 * k + x) = k - x 个硬币被翻转。

把第二堆全翻转一下就可以了。

int n, k;
cin >> n >> k;
cout << string(k, '1') + string(n - k, '4');
posted @ 2025-05-27 11:37  Music163  阅读(1113)  评论(0)    收藏  举报