2023牛客暑假多校1 题解 | JorbanS

D-Chocolate

题意 n × m 的巧克力,甲乙每人选定 [x, y],吃掉 [0, 0][x, y] 之间矩形区域的巧克力,每次至少吃一个单位,吃完最后一块巧克力的人输,甲先吃

Tag 博弈论

题解 不妨设 n < m,有以下结论

  • n = 1, m = 1 时,乙 win
  • n = 1, m > 1 时,当甲选择 x = 1, y = 1 时候,乙选择 x = 1, y = m - 1,甲只能选择 x = 1, y = m,乙 win,但是甲可以选择执行乙的第一步操作,从而甲一定 win
  • n > 1, m > 1 时,同上个情况,甲永远可以领先一步下乙的操作,因此甲一定 win
#include <iostream>

using namespace std;
int n, m;
const string a = "Kelin";
const string b = "Walk Alone";

string solve() {
    if (n == 1 && m == 1) return b;
    return a;
}

int main() {
    cout << solve() << endl;
    return 0;
}

H-Matches

题意 给定序列 ab,交换一个其中一个序列的两个数使得 ∑ n i = 1 ∣ a i − b i ∣ \sum_{n}^{i = 1}\left | a_{i} - b_{i} \right | ni=1aibi 最小

题解 def:若 a[i] < b[i](a[i], b[i]) 称之为正序列,a[i] > b[i] 称之为反序列

target: 求正序列和逆序列的最大重叠区间

输入 xy,按照 y 倒序排序,保证接下来枚举的时候 y 依次递减

对于逆序序列,设最大重叠区间为 [l, r],即 v[i].d < 0,维护 m1 = min(m1, v[i].x)l = max(v[i].x, m2)r = v[i].y(因为已保证 y 依次递减)。

对于正序序列,操作同上。进行 swap() 操作后,res -= len * 2

在这里插入图片描述

#include <iostream>
#include <algorithm>

using namespace std;
typedef long long ll;
const int N = 1e6 + 2;
int n;

struct Node {
    int x, y, d;
    bool operator < (const Node & W) const {
        return y > W.y;
    }
} v[N];

int main() {
    cin >> n;
    for (int i = 0; i < n; i ++) cin >> v[i].x;
    for (int i = 0; i < n; i ++) cin >> v[i].y;
    ll res = 0;
    for (int i = 0; i < n; i ++) {
        v[i].d = v[i].y - v[i].x;
        if (v[i].d < 0) swap(v[i].x, v[i].y);
        res += abs(v[i].d);
    }
    sort(v, v + n);
    int m1 = 2e9, m2 = 2e9, len = 0;
    for (int i = 0; i < n; i ++) {
        if (v[i].d < 0) {
            len = max(len, v[i].y - max(v[i].x, m2));
            m1 = min(m1, v[i].x);
        } else if (v[i].d > 0) {
            len = max(len, v[i].y - max(v[i].x, m1));
            m2 = min(m2, v[i].x);
        }
    }
    cout << res - (ll)len * 2 << endl;
    return 0;
}

J-Roulette

题意 n 喜欢一个游戏,第一次投入 x[i] = 1,之后有一半几率赢 x[i],有一半几率失去 x[i],赢一局后下一次投入 x[i] = 1,输了投入 x[i] = 2 * x[i - 1],初始有 n 元,问有多少几率能赢至 n + m

Tag 快速幂 逆元

题解 快速幂求逆元,可由费马小定理得出 inline int qpow(ll a, int b = mod - 2, int m = mod)

n 为质数时,可以用快速幂求逆元:
a / b ≡ a * x (mod n)
两边同乘b可得 a ≡ a * b * x (mod n)
1 ≡ b * x (mod n)
b * x ≡ 1 (mod n)
由费马小定理可知,当n为质数时
b ^ (n - 1) ≡ 1 (mod n)
拆一个b出来可得 b * b ^ (n - 2) ≡ 1 (mod n)
故当n为质数时,b的乘法逆元 x = b ^ (n - 2)

不难注意到,以”输输输……输赢”为一个周期,可以赢到一块钱,因而需要经过 m 个这样的周期。每个周期不会输光的条件:如果当前有 x 元,那么不能连输超过 r 次,r 是最大满足 2 r − 1 ≤ x 2^{r} - 1 \le x 2r1x 的正整数,成功的概率为 1 − ( 1 2 ) r 1 - (\frac{1}{2})^{r} 1(21)r。再从 n 枚举到 n + m,此时 n o w = log ⁡ 2 ( x + 1 ) now = \log_{2}{(x + 1)} now=log2(x+1),每次区间是 [ 2 r − 1 − 1 , 2 r + 1 − 2 ] [2^{r - 1} - 1, 2^{r + 1} - 2] [2r11,2r+12],每个区间的长度为 r - n,几率最后累乘即可

对于其中的快速幂求逆元运算,其中 ( 1 2 ) r = 2 − r = 2 − r + 1 − 1 = 2 − r + 1 × 2 m o d − 2 = 2 m o d − r − 1 (\frac{1}{2})^{r} = 2^{-r} = 2^{-r + 1 - 1} = 2^{-r + 1} \times 2^{mod - 2} = 2^{mod - r - 1} (21)r=2r=2r+11=2r+1×2mod2=2modr1

#include <iostream>
#include <cmath>

using namespace std;
typedef long long ll;
const int mod = 998244353;
int n, m;

inline int qpow(ll a, int b = mod - 2, int m = mod) {
    ll res = 1;
    while (b) {
        if (b & 1) (res *= a) %= m;
        (a *= a) %= m;
        b >>= 1;
    }
    return res;
}

int main() {
    cin >> n >> m;
    m += n;
    ll res = 1;
    while (n < m) { // m 是指最后一个区间的右端点的下一个
        int now = log2(n + 1);
        int r = min(m, (1 << (now + 1)) - 1);
        int powValue = 1 - qpow(2, mod - now - 1) + mod; // 加上 mod 是为了防止负数
        (res *= qpow(powValue, r - n)) %= mod;
        n = r;
    }
    cout << res << endl;
    return 0;
}

K-Subdivision

题意 n 个节点 m 条边的无向图,可以进行无限次操作,每次选择一条边,断开,并添加一个点,分别与原来的两个点相连,求能存在满足 dis <= k 的最多有多少个点

题解vector 记录每个节点的邻接表,从根节点 1 依次遍历,每次 res ++,如果是叶节点 res += k - d[t],如果没有被遍历过,则标记 vis[i] = 1,记录父节点为 p[i] = t,使得下次不能逆向遍历,同时更新距离 d[i] = d[t] + 1,若 d[i] <= k 则推入 queue 中;若被遍历过,则 res += k - d[t]

#include <iostream>
#include <vector>
#include <queue>

using namespace std;
typedef long long ll;
const int N = 1e5 + 2;
int n, m, k;

int main() {
    cin >> n >> m >> k;
    vector<vector<int>> v(n + 1);
    vector<int> d(n + 1, -1);
    vector<int> p(n + 1);
    vector<int> st(n + 1);
    while (m --) {
        int a, b; cin >> a >> b;
        v[a].push_back(b), v[b].push_back(a);
    }
    queue<int> q;
    q.push(1);
    d[1] = 0;
    st[1] = 1;
    ll res = 0;
    while (!q.empty()) {
        int t = q.front();
        q.pop();
        res ++;
        if (v[t].size() == 1 && t != 1) res += k - d[t];
        for (auto i : v[t]) {
            if (p[t] == i) continue;
            if (st[i] == 0) {
                st[i] = 1;
                p[i] = t;
                d[i] = d[t] + 1;
                if (d[i] <= k) q.push(i);
            } else res += k - d[t];
        }
    }
    cout << res << endl;
    return 0;
}

另一种用度数求解的方法,妙啊 😉

先从1 开始 bfs,同时求出每个点到 1 的最短路。

考虑选什么边来加点,两种情况:

第一种,删除该边不影响任何一个点到 1 的最短距离。 如何寻找这样的边,若一个点到 1 的最短距离小于等于 k 且该点有 2 个以上的出度/入度,则存在出度/入度减 2 的这样的边。

第二种,删除该边影响某点到 1 的最短路,那么这样的整一段最多为答案提供 k 的贡献。

#include "bits/stdc++.h"

using namespace std;
using i64 = long long;

int main() {
    int n, m, k;
    cin >> n >> m >> k;
    vector<vector<int>> g(n);
    vector<int> deg(n);
    for (int i = 0; i < m; i++) {
        int u, v; cin >> u >> v;
        u--, v--;
        g[u].push_back(v), g[v].push_back(u);
        deg[u]++, deg[v]++;
    }
    queue<int> q;
    vector<int> dis(n, -1);
    q.push(0);
    dis[0] = 0;
    while (!q.empty()) {
        auto u = q.front();
        q.pop();
        for (auto &v : g[u]) {
            if (dis[v] == -1) {
                dis[v] = dis[u] + 1;
                q.push(v);
            }
        }
    }
    i64 ans = 1 + k * deg[0];
    for (int i = 1; i < n; i++)
        if (deg[i] >= 2 && dis[i] != -1 && dis[i] <= k)
            ans += 1LL * (k - dis[i]) * (deg[i] - 2);
    cout << ans << endl;
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

JorbanS

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值