Loading

[题解] 2023 CCPC 黑龙江省赛(A、B、C、E、F、G)The 18th Heilongjiang Provincial Collegiate Programming Contest


题目:https://codeforces.com/gym/104363

vp 赛时就过了 3 个题,调 B 调红了。

官方题解:第十八届黑龙江省大学生程序设计竞赛 - 题解

A

注意到前两个样例答案是 \(2^k−1\) ,写个快速幂测一下第三个样例果然是,直接交。

时间复杂度 \(O(\log{k})\) 。也可以手动一个一个乘,边乘边取模,那样是 \(O(k)\)

Code

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

const int mod = 998244353;

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;
}

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

    int n;
    cin >> n;
    cout << ksm(2, n - 1);

    return 0;
}

B

首先考虑怎样拿最优。

规则:可以 01 交替拿,拿完之后两边的合并到一起。

不难发现 连续的0(下文称 00) 和连续的1(下文称 11)阻挡了一次拿完。

如果我们每次 从中间 拿走一条链,分为种情况:两侧相同(都是 00、都是11),两侧不同(一侧是00、一侧是11)。

对于两侧相同的情况,拿完之后,左右两边原本分段的 2 条链 还是需要拿 2 次。

对于两侧不同的情况,拿完之后,左右两边原本分段的 2 条链 可以合并成 1 条。

如果我们每次 从两边 拿走一条链,原本需要拿的 1 侧还是需要拿 1 次。

综上不难发现最优解是先拿 两侧不同的情况,这比其他情况可以节省 1 次的拿。

如何计算答案呢?

注意到我们可以先拿 00 和 11 中间的 链,然后凑成 两侧不同的情况 。所以 两侧不同的情况 种数 一共有 \(\min\{cnt(00), cnt(11)\}\) 种。

暂时忽略 两侧不同的情况 。我们需要拿 \(cnt(00) + cnt(11) + 1\) 次,才能把串拿完。

现在我们只需要拿 \(cnt(00) + cnt(11) + 1 - \min\{cnt(00), cnt(11)\} = \max\{cnt(00), cnt(11)\} + 1\) 次就可以把链拿完。

对于这个数据范围(1e6),统计 00 和 11 的个数我们可以用线段树,在 pushup 和 query 的时候合并左右孩子的结果。我们需要记录每个区间的 和 还有 区间左右两侧是 0 还是 1(上传的时候用来判断是否能产生新的 00 和 11)。

时间复杂度 \(O(n\log{n}+q\log{n})\)

Code

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

const int maxn = 1e6 + 6;

struct SegTree {
    struct Node {
        int l, r, v0, v1;
        bool L, R, laz;
    } t[maxn * 4];

    int a[maxn];

    inline void pushup(int p) {
        auto &me = t[p];
        auto &lc = t[p << 1], rc = t[p << 1 | 1];
        me.v0 = lc.v0 + rc.v0;
        me.v1 = lc.v1 + rc.v1;
        me.v1 += lc.R == rc.L && lc.R == 1;
        me.v0 += lc.R == rc.L && lc.R == 0;
        me.L = lc.L;
        me.R = rc.R;
    }

    inline void pushdown(int p) {
        auto &lc = t[p << 1], &rc = t[p << 1 | 1];
        if (t[p].laz) {
            swap(lc.v0, lc.v1);
            swap(rc.v0, rc.v1);
            lc.L = !lc.L;
            lc.R = !lc.R;
            rc.L = !rc.L;
            rc.R = !rc.R;
            t[p << 1].laz ^= t[p].laz;
            t[p << 1 | 1].laz ^= t[p].laz;
            t[p].laz = 0;
        }
    }

    void build(int p, int l, int r) {
        t[p].l = l, t[p].r = r;
        t[p].L = a[l], t[p].R = a[r];
        t[p].laz = 0;
        t[p].v0 = t[p].v1 = 0;
        if (l == r) {
            return;
        }
        int mid = l + r >> 1;
        build(p << 1, l, mid);
        build(p << 1 | 1, mid + 1, r);
        pushup(p);
    }

    void add(int p, int l, int r) {
        if (l <= t[p].l && t[p].r <= r) {
            swap(t[p].v0, t[p].v1);
            t[p].L = !t[p].L;
            t[p].R = !t[p].R;
            t[p].laz = !t[p].laz;
            return;
        }
        pushdown(p);
        int mid = t[p].l + t[p].r >> 1;
        if (l <= mid)
            add(p << 1, l, r);
        if (r > mid)
            add(p << 1 | 1, l, r);
        pushup(p);
    }

    array<int, 4> query(int p, int l, int r) {
        if (l <= t[p].l && t[p].r <= r) {
            return {t[p].L, t[p].R, t[p].v0, t[p].v1};
        }
        pushdown(p);
        int mid = t[p].l + t[p].r >> 1;
        int L1, R1, L2, R2, v0 = 0, v1 = 0;
        bool ok1 = 0, ok2 = 0;
        if (l <= mid) {
            auto res = query(p << 1, l, r);
            L1 = res[0];
            R1 = res[1];
            v0 += res[2];
            v1 += res[3];
            ok1 = 1;
        }
        if (r > mid) {
            auto res = query(p << 1 | 1, l, r);
            L2 = res[0];
            R2 = res[1];
            v0 += res[2];
            v1 += res[3];
            ok2 = 1;
        }
        if (ok1 && ok2) {
            v0 += R1 == L2 && R1 == 0;
            v1 += R1 == L2 && R1 == 1;
            return {L1, R2, v0, v1};
        } else if (ok1) {
            return {L1, R1, v0, v1};
        } else {
            return {L2, R2, v0, v1};
        }
    }
} T;

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

    int n, Q;
    cin >> n >> Q;
    for (int i = 1; i <= n; i++) {
        char x;
        cin >> x;
        T.a[i] = (x - '0');
    }
    T.build(1, 1, n);
    while (Q--) {
        char op;
        int l, r;
        cin >> op >> l >> r;
        if (op == 'Q') {
            auto res = T.query(1, l, r);
            cout << max(res[2], res[3]) + 1 << '\n';
        } else {
            T.add(1, l, r);
        }
        cout << "";
    }

    return 0;
}

C

这道题我找到两种解法。

#1 Brute Force

思路:

一共三个转盘,先把其中一个转成 0,然后操控剩下两个转成 0,

转第一个的时候可能会影响到剩下两个,不过没关系,不用管过程,直接枚举最终结果(每个转了几次)

最终的枚举最终的状态是一共转了几圈,检查是否合法,取最小值。

Step 1: 判断结果是否合法

设 3 个盘每个盘转了 \(t[i]\) 下, \(sum = t[0] + t[1] + t[2]\)

因为一次转 2 个,所以 sum 必须是偶数

因为每次转的两个必须不一样,所以最大的那个不能超过 sum / 2

Step 2: 枚举所有可能状况

最终状态一定是三个盘都转好的。

根据 Step 1,我们需要知道每个转盘在结束时转了几下。

首先 i-th 转盘一开始就被转了 \(x[i]\) 下。我们计算好把他转好(转到0)需要 多少下,设为 \(z[i]\) 。结束的时候 i-th 转盘一定总共转了 \(z[i] + k * y[i]\) 下,我们可以枚举这个 \(k\)

\(z[i] = (y[i] - x[i]) \bmod y[i]\) (最后要取模防止 \(x[i]\) 本来就是 0)

按照 思路 所说,我们应当先把其中一个转好,这里我们假定为 0 号转盘。然后枚举 1号、2号转盘转的次数。

直接转好 0 号转盘是 \(z[i]\) 次。不过有一种特殊情况,当 y[1] 和 y[2] 都是奇数或者都是偶数且 y[0] 是奇数时,我们可以通过再转 y[0] 次 0 号转盘来创造合法情况,所以还可以转 \(z[0] + y[0]\) 次。

转好 1 号、2 号转盘 最多各不超过 \(z[i] + 1999*y[i]\) 次。因为本质上转 1 号 和转 2号 都是为了 转 0 而不得不转的。补偿环 0 的操作只能是 p 次「转 0&1」加 q 次「转 0&2」,它们对环 0 的净偏移量是 \((p*y[1]+q*y[2]) \bmod y[0]\) ,多转没用。

然后我们再枚举一遍 先转好 1 号转盘和先转好 2 号转盘的情况。

时间复杂度 \(O(T*\max\{x_{i}\}*\max\{y_{i}\})\) ,这里循环次数写成常数应该可以循环展开,会快很多。

Code

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

const int INF = 1e9;

void solve() {
    int x[3] = {}, y[3] = {}, z[3] = {};
    for (int i = 0; i < 3; i++) {
        cin >> x[i];
    }
    for (int i = 0; i < 3; i++) {
        cin >> y[i];
        // 看看 第 i 个盘 还需要转多少下 才能到 0
        z[i] = (y[i] - x[i]) % y[i];
    }

    int ans = INF;
    auto work = [&](array<int, 3> t) {
        for (int i = 0; i < 3; i++) {
            t[i] += z[i];
        }
        int sum = t[0] + t[1] + t[2];
        if (sum % 2 == 0 && max({t[0], t[1], t[2]}) <= sum / 2) {
            ans = min(ans, sum / 2);
        }
    };

    for (int i = 0; i < 2; i++) {
        for (int j = 0; j < 2000; j++) {
            for (int k = 0; k < 2000; k++) {
                work({i * y[0], j * y[1], k * y[2]});
            }
        }
    }
    swap(z[0], z[1]);
    swap(y[0], y[1]);
    for (int i = 0; i < 2; i++) {
        for (int j = 0; j < 2000; j++) {
            for (int k = 0; k < 2000; k++) {
                work({i * y[0], j * y[1], k * y[2]});
            }
        }
    }
    swap(z[0], z[1]);
    swap(y[0], y[1]);

    swap(z[0], z[2]);
    swap(y[0], y[2]);
    for (int i = 0; i < 2; i++) {
        for (int j = 0; j < 2000; j++) {
            for (int k = 0; k < 2000; k++) {
                work({i * y[0], j * y[1], k * y[2]});
            }
        }
    }
    if (ans == INF) {
        cout << -1 << '\n';
    } else {
        cout << ans << '\n';
    }
}

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

#2 题解思路

思路在官方题解里有。下面的代码使用 o4-mini 生成的(太强了,一发就过)。实现过程写在注释里了。

时间复杂度 \(O(y_{0} \log y_{0} + lcm(y_{1}, y_{2}))\)

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

// 预处理:求 l = lcm(y1, y2)。
// 求解一个长度为 y0 的数组 f,表示对于任意模 y0 的余数 w,
//      最小的可表示成 p·y1+q·y2 且 ≡ w (mod y0) 的值。
// 建图:节点 0…y0−1,
// 边 u→(u+y1)%y0,权重 y1
// 边 u→(u+y2)%y0,权重 y2
// 从 0 起跑,跑 Dijkstra得最短路 dist[w]。
// 枚举 t0 = 0…l−1,计算当前最小总步数:
// t1 = (y2 − (x2 + t0) % y2) % y2
// t2 = (y1 − (x1 + t0) % y1) % y1
// S = (x0 + t1 + t2) % y0
// 还需加上的额外步数 s = dist[(y0 − S) % y0] (若 dist[...] 为无穷,跳过)
// 总步数 = t0 + t1 + t2 + s,取最小。
// 若最终最小值仍为无穷,输出 −1,否则输出该最小值。
// 复杂度:O(y0 log y0 + l),其中 l = lcm(y1,y2),y_i ≤2000,T≤10。

const i64 INF = 1e18;

void solve() {
    int x[3] = {}, y[3] = {};
    for (int i = 0; i < 3; i++) cin >> x[i];
    for (int i = 0; i < 3; i++) cin >> y[i];

    int y0 = y[0], y1 = y[1], y2 = y[2];
    // Dijkstra 求 dist[w] = min{p*y1+q*y2 | (p*y1+q*y2)%y0 == w}
    // 这里的 dist[] 就是转 p 步 y0 所需要的 最小转数
    vector<i64> dist(y0, INF);
    priority_queue<pair<i64, int>, vector<pair<i64, int>>, greater<>> pq;
    dist[0] = 0; pq.emplace(0, 0);
    while (!pq.empty()) {
        auto [d, u] = pq.top(); pq.pop();
        if (d > dist[u]) continue;
        for (int inc : {y1, y2}) {
            int v = (u + inc) % y0;
            i64 nd = d + inc;
            if (nd < dist[v]) {
                dist[v] = nd;
                pq.emplace(nd, v);
            }
        }
    }

    int L = lcm(y1, y2);
    i64 ans = INF;
    for (int t0 = 0; t0 < L; t0++) {
        int t1 = (y2 - (x[2] + t0) % y2) % y2;
        int t2 = (y1 - (x[1] + t0) % y1) % y1;
        int S = (x[0] + t1 + t2) % y0;
        int need = (y0 - S) % y0;
        if (dist[need] == INF) continue;
        ans = min(ans, i64(t0) + t1 + t2 + dist[need]);
    }

    cout << (ans == INF ? -1 : ans) << '\n';
}

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

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

E

注意到数据范围非常小,直接按照题意模拟即可。

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

Code

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

int n, m;
int zi, mu;
int vis[11];

void dfs(int x) {
    if (x == n) {
        mu++;
        if (!vis[n]) {
            zi++;
        }
    }
    if (x <= m) {
        for (int i = 1; i <= n; i++) {
            if (!vis[i]) {
                vis[i] = 1;
                dfs(x + 1);
                vis[i] = 0;
            }
        }
    } else {
        if (!vis[x]) {
            vis[x] = 1;
            dfs(x + 1);
            vis[x] = 0;
        } else {
            for (int i = 1; i <= n; i++) {
                if (!vis[i]) {
                    vis[i] = 1;
                    dfs(x + 1);
                    vis[i] = 0;
                }
            }
        }
    }
}

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

    cin >> n >> m;
    dfs(1);

    cout << fixed << setprecision(10);
    cout << 1.0 * zi / mu << '\n';

    return 0;
}

F

题意说要把这个树捋成一条链,答案就是 。

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

Code

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

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

int ans = 0;

void dfs(int x, int fa) {
    if (g[x].empty()) {
        return;
    }
    ans += (int)g[x].size() - 1;
    for (auto &v : g[x]) {
        if (v == fa) {
            continue;
        }
        dfs(v, x);
    }
}

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

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

    cout << ans << '\n';


    return 0;
}

G

官方题解思路明确。关键有以下几点:

  1. 计算 OAB 的面积,是 \(\frac{1}{2} \cdot A \times B = \frac{1}{2} \left| A \right|\left| B \right|sin\angle AOB\) 。这里直接算叉积就好, 是 \(A\times B=Ax*By - Ay*Bx\)
  2. 多边形的重心,是所有顶点矢量和相加,然后 x y 坐标分别 除以顶点个数。
  3. 利用叉积乘自身为 0 的性质,化简公式,消去变量 "A 的顶点坐标和"。
  4. 叉乘满足分配律,将分子化为能排序的形式。
  5. 贪心求解,使分子尽可能的大,枚举分母。

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

Code

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

struct Pt {
    i64 x, y;
    Pt() {
        x = 0;
        y = 0;
    }
    i64 operator^(const Pt &b) const {
        return x * b.y - y * b.x;
    }
};

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

    int n;
    cin >> n;
    vector<Pt> v(n + 1);
    Pt S;
    for (int i = 1; i <= n; i++) {
        cin >> v[i].x >> v[i].y;
        S.x += v[i].x;
        S.y += v[i].y;
    }
    vector<i64> a(n + 1);
    for (int i = 1; i <= n; i++) {
        a[i] = S ^ v[i];
    }

    sort(a.begin() + 1, a.end(), greater<>());
    i64 pre = 0;
    long double ans = 0;
    // |A| >= 1 && |B| >= 1
    for (int i = 1; i < n; i++) {
        pre += a[i];
        ans = max(ans, 1.0L * pre / (2.0L * i * (n - i)));
    }
    cout << fixed << setprecision(12) << ans << '\n';

    return 0;
}

I

直接没看懂题目啊,看了半天没看懂。

posted @ 2025-04-30 20:26  Music163  阅读(116)  评论(0)    收藏  举报