2023牛客暑期多校训练营1

2023牛客暑期多校训练营1

D-Chocolate

题意

​ 二人博弈,每局给出一个 n × m n×m n×m的巧克力,每次操作可以选择一个点 ( x , y ) (x,y) (x,y)然后拿走所有 ( i ≤ x & & j ≤ y ) (i \leq x \&\&j\leq y) (ix&&jy)的巧克力,拿走最后一块的人输

分析

​ 首先当 n = 1 , m = 1 n=1,m=1 n=1,m=1时,后手一定赢,那么再考虑先手的情况,可知当存在后继必败那么当前一定必胜,如果所有后继都必胜那么当前一定必败,那么先手在这种情况下一定会找到后手必败的情况走下去,先手一定占优的,所以先手在其他情况下一定是必胜的

AC代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int n, m;
    cin >> n >> m;
    if (n == 1 && m == 1) {
        cout << "Walk Alone\n";
    } else {
        cout << "Kelin\n";
    }
    return 0;
}

J-Roulette

题意

​ 玩家初始有 n n n块钱,如果每次投 x x x块钱,有一半概率输掉 x x x块钱,一半概率赢 2 x 2x 2x块钱,当前策略是:

  • 如果上一把赢了,这一把投 x i = 1 x_{i}=1 xi=1块钱
  • 如果上一把输了,这一把投 x i = 2 x i − 1 x_{i}=2x_{i-1} xi=2xi1块钱

问玩家有多大概率净赚 m m m块钱后离开

分析

​ 根据手玩的结果发现,在赢之前无论输多少回合,赢一次以后净赚1块钱,因此需要考虑 m m m个输赢周期,并且每个周期不能把所有的钱都输光,假设当前有 x x x元钱,最多能够连输 r r r回合,其中 2 r − 1 ≤ x 2^{r}-1 \leq x 2r1x,那么赢的概率就是 1 − ( 1 2 ) r 1-(\frac{1}{2})^{r} 1(21)r,然后就是枚举 m m m r r r,计算成功概率

∏ i = n + 1 n + m ( 1 − 1 2 l o g 2 i ) \prod_{i=n+1}^{n+m}(1-\frac{1}{2^{log_{2}i}}) i=n+1n+m(12log2i1),注意不可暴力计算,因为在一段连续的当中,概率都是同样的值,所以只需要计算 l o g 2 ( n + m ) log_{2}(n+m) log2(n+m)

AC代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
const int mod = 998244353;
int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    LL n, m;
    cin >> n >> m;
    function<LL(LL, LL)> qp = [&](LL a, LL b) {
        LL res = 1;
        for (; b; b >>= 1, a = a * a % mod) {
            if (b & 1) {
                res = res * a % mod;
            }
        }
        return res;
    };
    LL ans = 1, inv = qp(2, mod - 2);
    for (int i = n + 1; i <= n + m;) {
        int now = __lg(i);
        int nxt = min(n + m, (1LL << (now + 1)) - 1);
        ans = ans * qp((1 - qp(inv, now) + mod) % mod, nxt - i + 1) % mod;
        i = nxt + 1;
    }
    cout << ans << '\n';
    return 0;
}

K-Subdivision

题意

​ 给定一张 n n n个点 m m m条边的图,每条边权值为1,可操作任意次,一次操作可以将一条边分裂成长度任意的链,问处理若干次或不处理后,从1号节点出发走 k k k步最多走到多少个节点

分析

​ 根据题意,发现答案的下限是1+(节点1的度)*k,因为即便其他点与节点1形成了一个环,但只有一条路径会真正的影响其他点到节点1的距离,因此处理出所有点到节点1的最短距离,确定一棵以节点1为根、以最短距离的边形成的树,其他的非树上的边就是对其他节点计算没有影响的边,可以随意分裂。因此必然是让树上所有的叶子节点都最深,形成的到节点1的路径最长,所以是(节点1的度)*k。

​ 然后考虑非树上的边,因为删除这条边对其他节点最短距离的计算没有影响,所以就尽可能的让其变长,以达到答案最大,那么如何找到这样的边,需要确定的就是他们连接的点有什么样的性质。根据画图可以发现,它所连接的点中一定存在该点到节点1的距离小于k并且该点的度大于2,距离小于k容易理解,这种可以分成两类,这条边连接了同一层的两个点,或者连接了不同层的两个点,但去掉这条边不影响深度计算,若是连接了同一层的两个点,并且连接的点的度大于2,说明连接的点是最短路径上的一部分,而这条边与路径无关,所以可以任意延伸,不同层的两个点也是同理,该条边如何延伸,对其他点最短距离的计算没有影响

AC代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int n, m, k;
    cin >> n >> m >> k;
    vector<int> deg(n + 1);
    vector<vector<int> > G(n + 1);
    for (int i = 1; i <= m; i++) {
        int u, v;
        cin >> u >> v;
        G[u].push_back(v);
        G[v].push_back(u);
        deg[u]++;
        deg[v]++;
    }
    queue<int> q;
    vector<int> dist(n + 1, 0x3f3f3f3f);
    dist[1] = 0;
    q.push(1);
    while (!q.empty()) {
        int u = q.front();
        q.pop();
        for (auto v : G[u]) {
            if (dist[v] == 0x3f3f3f3f) {
                dist[v] = dist[u] + 1;
                q.push(v);
            }
        }
    }
    LL ans = 1 + 1LL * deg[1] * k;
    for (int i = 2; i <= n; i++) {
        if (deg[i] > 2 && dist[i] < k) {
            ans = ans + 1LL * (k - dist[i]) * (deg[i] - 2);
        }
    }
    cout << ans << '\n';
    return 0;
}

H-Matches

题意

​ 给定两个长度为 n n n的序列,令 d = ∑ i = 1 n ∣ a i − b i ∣ d=\sum_{i=1}^{n}|a_{i}-b_{i}| d=i=1naibi,要求在一行中至多允许交换一次两个数,问 d d d最小为多少

分析

​ 首先看看什么情况下会导致 d d d变小,根据在数轴上画图模拟可知,令A为 a i ≤ b i a_{i} \leq b_{i} aibi类线段,B为 a i > b i a_{i} > b_{i} ai>bi类线段,发现只有一个线段为A类,一个线段为B类,且两个线段有交集时才会使 d d d变小,其他情况均为不变或变大,而且变小的长度是两条线段交集长度的两倍,然后问题就变成了求任意两个线段中最大的重叠的部分。做法就是以左端点大小排序,保证左端点一定是递增的,然后分别维护两类线段右端点的最大值,因为左侧一定是递增的,所以当右端点最大值大于当前线段的左端点时,一定是有交集的,求出最大的即可

AC代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
struct node {
    LL l, r, flag;
};
int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int n;
    cin >> n;
    vector<LL> a(n), b(n);
    for (int i = 0; i < n; i++) {
        cin >> a[i];
    }
    for (int i = 0; i < n; i++) {
        cin >> b[i];
    }
    vector<node> c(n);
    LL ans = 0;
    for (int i = 0; i < n; i++) {
        ans = ans + abs(a[i] - b[i]);
        if (a[i] <= b[i]) {
            c[i] = {a[i], b[i], 0};
        } else {
            c[i] = {b[i], a[i], 1};
        }
    }
    LL maxx = 0;
    sort(c.begin(), c.end(), [](node a, node b) {
        if (a.l != b.l) {
            return a.l < b.l;
        }
        return a.r < b.r;
    });
    LL max0 = -0x3f3f3f3f, max1 = -0x3f3f3f3f;
    for (int i = 0; i < n; i++) {
        if (c[i].flag == 0) {
            if (max1 > c[i].l) {
                maxx = max(maxx, min(c[i].r, max1) - c[i].l);
            }
            max0 = max(max0, c[i].r);
        } else {
            if (max0 > c[i].l) {
                maxx = max(maxx, min(c[i].r, max0) - c[i].l);
            }
            max1 = max(max1, c[i].r);
        }
    }
    cout << ans - 2 * maxx << '\n';
    return 0;
}

L-Three Permutations

题意

​ 给定三个长度为 n n n的排列 a a a b b b c c c,初始给出 x , y , z x,y,z x,y,z全都等于1,每一秒, x , y , z x,y,z x,y,z变成 a y , b z , c x a_{y},b_{z},c_{x} ay,bz,cx,然后给出 x ′ , y ′ , z ′ x',y',z' x,y,z,问最短多少秒 ( x , y , z ) (x,y,z) (x,y,z)可以变成 ( x ′ , y ′ , z ′ ) (x',y',z') (x,y,z)

分析

​ 通过手玩发现,对于 x : a b c a b c a b c … , y : b c a b c a b c a … , z : c a b c a b c a b … x:abcabcabc\dots,y:bcabcabca\dots,z:cabcabcab\dots x:abcabcabc,y:bcabcabca,z:cabcabcab,可以发现规律,每三次变换为一个循环,因此我们可以分别对三种循环进行运算,即第 3 t , 3 t + 1 , 3 t + 2 3t,3t+1,3t+2 3t,3t+1,3t+2秒,判断在这三种循环中是否存在一种方式可以使得 ( x , y , z ) (x,y,z) (x,y,z)变成 ( x ′ , y ′ , z ′ ) (x',y',z') (x,y,z),对于每一种循环,可以得到三个线性同余方程
{ x ≡ a   ( m o d   a ′ ) x ≡ b   ( m o d   b ′ ) x ≡ c   ( m o d   c ′ ) \begin{align} \left\{ \begin{array}{c} x\equiv a\ (mod\ a') \\ x\equiv b\ (mod\ b') \\ x\equiv c\ (mod\ c') \end{array} \right. \end{align} xa (mod a)xb (mod b)xc (mod c)
​ 因为 ( a ′ , b ′ , c ′ ) (a',b',c') (a,b,c)不一定互素,因此使用扩展中国剩余定理解决问题,其中 a a a即为数字第一次出现的秒数, a ′ a' a为当前这种数字在该循环下多少秒循环一次,b,c同理,因为在查询前预处理的是 6 n 6n 6n的时间长度,因此循环长度为n的话每种数也至少出现三次,可以直接用差值代表模数,不存在只出现一次且以后再也不出现这个数字的情况

AC代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int n, q, a[100010], b[100010], c[100010];
vector<int> X[3][100010], Y[3][100010], Z[3][100010];
LL exgcd(LL a, LL b, LL &x, LL &y) {
    if (!b) {
        x = 1;
        y = 0;
        return a;
    }
    LL d = exgcd(b, a % b, y, x);
    y -= a / b * x;
    return d;
}
//merge x % b == a && x % d == c
void merge(LL &a, LL &b, int c, int d) {
    if (a == -1 && b == -1) {
        return;
    }
    LL x, y;
    int g = exgcd(b, d, x, y);
    //bx + dy = gcd(b, d);
    if ((c - a) % g != 0) {
        a = b = -1;
        return;
    }
    d /= g;
    LL t = (c - a) / g % d * x % d;
    if (t < 0) {
        t += d;
    }
    a = b * t + a;
    b = b * d;
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
    }
    for (int i = 1; i <= n; i++) {
        cin >> b[i];
    }
    for (int i = 1; i <= n; i++) {
        cin >> c[i];
    }
    int x = 1, y = 1, z = 1;
    for (int i = 0; i <= 6 * n; i++) {
        X[i % 3][x].push_back(i);
        Y[i % 3][y].push_back(i);
        Z[i % 3][z].push_back(i);
        int nxtx = a[y], nxty = b[z], nxtz = c[x];
        x = nxtx;
        y = nxty;
        z = nxtz;
    }
    cin >> q;
    while (q--) {
        cin >> x >> y >> z;
        LL ans = 1e18;
        for (int i = 0; i <= 2; i++) {
            if (X[i][x].empty() || Y[i][y].empty() || Z[i][z].empty()) {
                continue;
            }
            int xx = X[i][x][1] - X[i][x][0];
            int yy = Y[i][y][1] - Y[i][y][0];
            int zz = Z[i][z][1] - Z[i][z][0];
            LL a = 0, b = 1;
            //x % b == a
            merge(a, b, X[i][x][0], xx);
            merge(a, b, Y[i][y][0], yy);
            merge(a, b, Z[i][z][0], zz);
            if (a != -1) {
                ans = min(ans, a);
            }
        }
        if (ans == 1e18) {
            cout << "-1\n";
        } else {
            cout << ans << '\n';
        }
    }
    return 0;
}

M-Water

题意

​ 给定两个容积分别为A,B的水杯,每次可以执行下列四种操作之一:

  • 把其中一个杯子装满水
  • 把其中一个杯子的水全部倒掉
  • 把其中一个杯子中的水全部喝掉
  • 把其中一个杯子的水倒入另一个杯子中,但不能溢出

问喝掉x体积的水至少要操作多少次

分析

​ 根据题意不难想到,想要得到可行解,就是在找 A i + B j = x Ai+Bj=x Ai+Bj=x的通解 ( i , j ) (i,j) (i,j)的最小值,所以先判断 g c d ( A , B ) ∣ x gcd(A,B)|x gcd(A,B)x,然后再考虑怎么把解和操作数对应起来。

​ 首先考虑当 i ≥ 0 , j ≥ 0 i\geq 0,j\geq 0 i0,j0时,相当于就是往容积为A的水杯中加入i次水,往容积为B的水杯中加入j次水,那么再算上每次倒入和喝掉的总操作数就是 2 ( i + j ) 2(i+j) 2(i+j),再考虑当有一方为负数的情况,假设j为负数,i为正数,易知i和j不能同时为负数。因为j为负数,i为正数,那么一定可以知道 A i > x Ai>x Ai>x,因此需要往容积为B的水杯中倒出一些水进行中和,也就是减去 B ∣ j ∣ B|j| Bj杯水,先观察容积为A的水杯的操作,一个是倒入,一个是喝掉,还一个是倒入容积为B的水杯中,再观察容积为B的水杯的操作,只有将杯中水倒出的操作,其实从A杯往B杯倒水的操作也可以算在B杯的操作中,那么一共的操作就是 2 ∣ i − j ∣ 2|i-j| 2∣ij,但还需要注意一点的就是,最后一次从B杯中倒出水是没有必要的一次操作,所以只需要进行 2 ∣ i − j ∣ − 1 2|i-j|-1 2∣ij1次操作即可。i为负数,j为正数也同理。

AC代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
LL exgcd(LL a, LL b, LL &x, LL &y) {
    if (!b) {
        x = 1;
        y = 0;
        return a;
    }
    LL d = exgcd(b, a % b, y, x);
    y -= a / b * x;
    return d;
}
void Solve() {
    LL a, b, x;
    cin >> a >> b >> x;
    LL ansx, ansy, ans = 1e18;
    LL g = exgcd(a, b, ansx, ansy);
    if (x % g != 0) {
        cout << "-1\n";
        return;
    }
    a /= g;
    b /= g;
    x /= g;
    ansx = (ansx % b) * (x % b) % b;
    ansy = (x - a * ansx) / b;
    for (int i = -10; i <= 10; i++) {
        LL xx = (ansx + b * i), yy = (ansy - a * i);
        if (xx >= 0 && yy >= 0) {
            ans = min(ans, 2 * (xx + yy));
        } else {
            ans = min(ans, 2 * abs(xx - yy) - 1);
        }
    }
    cout << ans << '\n';
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int T;
    cin >> T;
    while (T--) {
        Solve();
    }
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值