J - Roulette
题目大意
你可以玩游戏,规则如下:
-
对于每次游戏,你有 50 % 50\% 50% 的概率获胜,获胜会让你获得投入资金的两倍,失败则什么也没有
-
第一次游戏需要 1 1 1 块钱,游戏输了则下次需要投入 2 2 2 倍上一次投入的钱,赢了则恢复为需要 1 1 1 块钱
-
如果钱不够了你就无法进行下一次游戏
你有 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+1−1≤x<2i+2−1)
那么你当前轮失败的概率就是 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+1−1≤x<2j+2−1)
那么你到下一轮前失败的概率就是 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+(2i2i−1∗2j1)
由此类推即可推导出最后结果
但是数据范围过大不可以直接 O ( m ) O(m) O(m) 处理
考虑优化,可以发现在 x = 2 i + 2 − 1 x=2^{i+2}-1 x=2i+2−1 前,都是最多玩 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+2−1 元,从此时开始算,在到达前失败的概率就是 ∑ j = 0 k − 1 p ∗ ( 1 − p ) j \sum_{j=0}^{k-1}p*(1-p)^j ∑j=0k−1p∗(1−p)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−(1−p)k=1−(2i2i−1)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+(1−fi)∗(1−(2i2i−1)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 的水,你可以进行以下操作:
-
将一只杯子装满水
-
将一只杯子里的水全部倒掉
-
将一只杯子里的水全部喝掉
-
将一只杯子里的水倒到另一只杯子,使这只杯子里的水倒完或者使另一只杯子装满
询问最少操作次数或者不可行
解题思路
假设 a > b a>b a>b
模拟可以发现有三种情况:
-
喝 x x x 杯 a a a 和 y y y 杯 b b b,次数为 2 ( x + y ) 2(x+y) 2(x+y)
-
倒 x x x 杯 a a a 来喝但将其中倒 y y y 次到 b b b 杯并每次倒掉,最后一次不用倒,次数为 2 ( x + y ) − 1 2(x+y)-1 2(x+y)−1
-
倒 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=x∗a+y∗b(倒掉就是 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 n≤16),保证此序列不为单调不递减,你需要给出一组操作,操作数 m ≤ 120 m\le120 m≤120,第 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 找出来,标记位置为 p p p
在所有序列中,如果 p p p 位置为 0 0 0,那么后面步骤中将这一位上的 1 1 1 换出去的操作可以忽略,而剩下的操作足以将其有序化,那么接下来的分析就先建立在 p p p 位为 1 1 1 的基础上
- 将它与后面所有的 1 1 1 进行一次交换,操作数为 a − 1 a- 1 a−1
这样就可以将另外序列中,在后面位中任何一位没有 1 1 1 的序列有效排序,那么剩下有问题的序列中的 1 1 1 必定比此序列多,那么下一步便可以保证如果 p p p 位上还有 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(n−p)(n−p+1)
-
再将 p p p 位置与 b − 1 b-1 b−1 到 p + 1 p+1 p+1 的所有数进行一次交换(注意必须反向以保证 1 1 1 尽可能在靠后的位置以保证有序),操作数为 b − p − 2 b-p-2 b−p−2
以上操作可以保证在此序列不有序的情况下将 p p p 到 n n n 全部有序化
-
之后再将第 1 1 1 到 p − 1 p-1 p−1 的所有数与 n n n 到 p p p 的所有数两两交换(这里的反向同理),操作数为 ( p − 1 ) ∗ ( n − p + 1 ) (p-1)*(n-p+1) (p−1)∗(n−p+1)
-
最后一步将第 1 1 1 到 p − 1 p-1 p−1 的所有数两两交换使其有序,操作数为 ( p − 1 ) ( p − 2 ) 2 \frac{(p-1)(p-2)}{2} 2(p−1)(p−2)
通过计算可以发现以上操作数之和不大于 n ( n − 1 ) 2 = 120 \frac{n(n-1)}{2}=120 2n(n−1)=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;
}