23.7.17 牛客暑期多校1部分题解

文章讲述了两个与数学和编程相关的问题。第一个问题是关于一个赌博游戏的概率计算,玩家在每次游戏中有50%的胜率,目标是计算在有限资金下增加特定金额的概率。第二个问题是一个涉及水杯的数学问题,探讨如何通过最少操作次数将不同容量的杯子中的水调整到特定总量。这两个问题都涉及到策略制定和数学建模,如容斥原理、动态规划和扩展欧几里得算法的应用。
摘要由CSDN通过智能技术生成

J - Roulette

题目大意

你可以玩游戏,规则如下:

  1. 对于每次游戏,你有 50 % 50\% 50% 的概率获胜,获胜会让你获得投入资金的两倍,失败则什么也没有

  2. 第一次游戏需要 1 1 1 块钱,游戏输了则下次需要投入 2 2 2 倍上一次投入的钱,赢了则恢复为需要 1 1 1 块钱

  3. 如果钱不够了你就无法进行下一次游戏

你有 n n n 块钱,你想让你的钱变成 n + m n+m n+m,问概率为多少

解题思路

将从 1 1 1 块钱开始玩到赢一次或者无法进行下一次操作称为一轮

考虑容斥,计算失败的概率

模拟之后可以发现,无论输多少次,只要赢一次,你就会比这轮开始前多 1 1 1 块钱

那么题目就转化成了赢 m m m 次的概率

假设你此时有 x x x 元,对于当前轮,你最多可以玩 i i i 次( 2 i + 1 − 1 ≤ x < 2 i + 2 − 1 2^{i+1}-1\le x<2^{i+2}-1 2i+11x<2i+21

那么你当前轮失败的概率就是 1 2 i \frac{1}{2^i} 2i1

如果赢了,下一轮你就有 x + 1 x+1 x+1 元,最多可以玩 j j j 次( 2 j + 1 − 1 ≤ x < 2 j + 2 − 1 2^{j+1}-1\le x<2^{j+2}-1 2j+11x<2j+21

那么你到下一轮前失败的概率就是 1 2 i + ( 2 i − 1 2 i ∗ 1 2 j ) \frac{1}{2^i}+(\frac{2^i-1}{2^i}*\frac{1}{2^j}) 2i1+(2i2i12j1)

由此类推即可推导出最后结果

但是数据范围过大不可以直接 O ( m ) O(m) O(m) 处理

考虑优化,可以发现在 x = 2 i + 2 − 1 x=2^{i+2}-1 x=2i+21 前,都是最多玩 i i i 次,可以对于每个 i i i 处理,时间复杂度就会变成 O ( l o g m ) O(logm) O(logm)

p = 1 2 i p=\frac{1}{2^i} p=2i1

若需要玩 k k k 轮才能到 2 i + 2 − 1 2^{i+2}-1 2i+21 元,从此时开始算,在到达前失败的概率就是 ∑ j = 0 k − 1 p ∗ ( 1 − p ) j \sum_{j=0}^{k-1}p*(1-p)^j j=0k1p(1p)j

化简后得出失败概率为 1 − ( 1 − p ) k = 1 − ( 2 i − 1 2 i ) k 1-(1-p)^k=1-(\frac{2^i-1}{2^i})^k 1(1p)k=1(2i2i1)k

设最多玩 i i i 次情况下失败概率为 f i f_i fi

那么 f i + 1 = f i + ( 1 − f i ) ∗ ( 1 − ( 2 i − 1 2 i ) m i n ( m , 2 i + 2 ) − 2 i + 1 ) f_{i+1}=f_i+(1-f_i)*(1-(\frac{2^{i}-1}{2^{i}})^{min(m,2^{i+2})-2^{i+1}}) fi+1=fi+(1fi)(1(2i2i1)min(m,2i+2)2i+1)(代码中可能与此公式不同因为有预处理)

code

#include <bits/stdc++.h>
using namespace std;
const int MOD = 998244353;
int n, m, k;
long long ans, p[35], sum;
long long pw(long long a, int b)
{
    long long res = 1;
    while (b)
    {
        if (b & 1) res = res * a % MOD;
        a = a * a % MOD;
        b >>= 1;
    }
    return res;
}
int main()
{
    scanf("%d%d", &n, &m);
    p[1] = 1;
    for (int i = 2; i <= 32; ++ i) p[i] = p[i - 1] * 2;
    for (int i = 1; i <= 32; ++ i)
    {
        p[i] += p[i - 1];
        if (p[i] <= n) k = i;
    }
    ans = min(1ll * m, p[k + 1] - n);
    while (m)
    {
        long long res = pw(2ll, k);
        sum = (sum + (1ll - sum + MOD) % MOD * (1ll - pw((res - 1 + MOD) % MOD * pw(res, MOD - 2) % MOD, ans) + MOD) % MOD) % MOD;
        ++ k; m -= ans;
        ans = min(1ll * m, p[k + 1] - p[k]);
    }
    printf("%lld", (1ll - sum + MOD) % MOD);
    return 0;
}

M - Water

题目大意

你有 a , b a,b a,b 两只容量不同的杯子,你想恰好喝 n n n 的水,你可以进行以下操作:

  1. 将一只杯子装满水

  2. 将一只杯子里的水全部倒掉

  3. 将一只杯子里的水全部喝掉

  4. 将一只杯子里的水倒到另一只杯子,使这只杯子里的水倒完或者使另一只杯子装满

询问最少操作次数或者不可行

解题思路

假设 a > b a>b a>b

模拟可以发现有三种情况:

  1. x x x a a a y y y b b b,次数为 2 ( x + y ) 2(x+y) 2(x+y)

  2. x x x a a a 来喝但将其中倒 y y y 次到 b b b 杯并每次倒掉,最后一次不用倒,次数为 2 ( x + y ) − 1 2(x+y)-1 2(x+y)1

  3. y y y b b b 来喝但每次都要倒到 a a a 杯, a a a 杯倒满后喝 b b b 中剩下的水,次数为 2 y + 1 2y+1 2y+1

由此可以推断出其实 n = x ∗ a + y ∗ b n=x*a+y*b n=xa+yb(倒掉就是 x , y x,y x,y 是负数,第三种情况 x = − 1 x=-1 x=1

那么就可以用扩展欧几里得, x , y x,y x,y 都为正数时次数为 2 ( x + y ) 2(x+y) 2(x+y),有负数时为 2 ( ∣ x ∣ + ∣ y ∣ ) + 1 2(|x|+|y|)+1 2(x+y)+1

枚举最小非负解和最大负解然后求次数最小值就可以了

no code

A - Almost Correct

题目大意

给你个长度为 n n n 的01序列( n ≤ 16 n\le16 n16),保证此序列不为单调不递减,你需要给出一组操作,操作数 m ≤ 120 m\le120 m120,第 i i i 次操作包括两个元素 x i ,   y i x_i,\space y_i xi, yi,含义是将第 x i x_i xi 个字符与第 y i y_i yi 个字符交换,要求这组操作能将除给定的01序列外的另外所有长度为 n n n 的01序列排序成单调不递减的序列。

解题思路

假设此序列有 a a a 1 1 1 b b b 0 0 0 因为此序列不为单调不递减,所以在前 b b b 个数中至少存在一个数为 1 1 1,我们仅针对于第一个 1 1 1,使它不会被排到后 a a a 个数中即可

具体操作:

  1. 将第一个 1 1 1 找出来,标记位置为 p p p

在所有序列中,如果 p p p 位置为 0 0 0,那么后面步骤中将这一位上的 1 1 1 换出去的操作可以忽略,而剩下的操作足以将其有序化,那么接下来的分析就先建立在 p p p 位为 1 1 1 的基础上

  1. 将它与后面所有的 1 1 1 进行一次交换,操作数为 a − 1 a- 1 a1

这样就可以将另外序列中,在后面位中任何一位没有 1 1 1 的序列有效排序,那么剩下有问题的序列中的 1 1 1 必定比此序列多,那么下一步便可以保证如果 p p p 位上还有 1 1 1 也能交换到合适的位置

  1. 再将 p p p 位置之后的所有数两两交换以保证 p + 1 p+1 p+1 n n n 有序,操作数为 ( n − p ) ( n − p + 1 ) 2 \frac{(n - p)(n - p + 1)}{2} 2(np)(np+1)

  2. 再将 p p p 位置与 b − 1 b-1 b1 p + 1 p+1 p+1 的所有数进行一次交换(注意必须反向以保证 1 1 1 尽可能在靠后的位置以保证有序),操作数为 b − p − 2 b-p-2 bp2

以上操作可以保证在此序列不有序的情况下将 p p p n n n 全部有序化

  1. 之后再将第 1 1 1 p − 1 p-1 p1 的所有数与 n n n p p p 的所有数两两交换(这里的反向同理),操作数为 ( p − 1 ) ∗ ( n − p + 1 ) (p-1)*(n-p+1) (p1)(np+1)

  2. 最后一步将第 1 1 1 p − 1 p-1 p1 的所有数两两交换使其有序,操作数为 ( p − 1 ) ( p − 2 ) 2 \frac{(p-1)(p-2)}{2} 2(p1)(p2)

通过计算可以发现以上操作数之和不大于 n ( n − 1 ) 2 = 120 \frac{n(n-1)}{2}=120 2n(n1)=120

code

#include <bits/stdc++.h>
using namespace std;
int t, n, a[20], p, k, m, x[125], y[125];
int main() {
    scanf("%d", &t);
    while (t --) {
        scanf("%d", &n);
        m = k = 0;
        for (int i = 1; i <= n; ++ i)
            scanf("%1d", &a[i]), k += a[i];
        for (int i = 1; i <= n; ++ i)
            if (a[i] == 1) {p = i; break;}
        for (int i = p + 1; i <= n; ++ i)
            if (a[i] == 1) x[++ m] = p, y[m] = i;
        for (int i = p + 1; i <= n; ++ i)
            for (int j = i + 1; j <= n; ++ j)
                x[++ m] = i, y[m] = j;
        for (int i = n - k; i > p; -- i)
            x[++ m] = p, y[m] = i;
        for (int i = 1; i < p; ++ i)
            for (int j = n; j >= p; -- j)
                x[++ m] = i, y[m] = j;
        for (int i = 1; i < p; ++ i)
            for (int j = i + 1; j < p; ++ j)
                x[++ m] = i, y[m] = j;
        printf("%d\n", m);
        for (int i = 1; i <= m; ++ i)
            printf("%d %d\n", x[i], y[i]);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值