# AtCoder Beginner Contest 189 题解

# Problem A - Slot (opens new window)

检查是否满足C1=C2=C3C_1=C_2=C_3即可。

  • 时间复杂度O(1)\mathcal{O}(1)
  • 空间复杂度O(1)\mathcal{O}(1)
参考代码 (Rust)
use proconio::input;
use proconio::marker::Chars;

fn main() {
    input! {
        s: Chars,
    }
    println!("{}", if s[0] == s[1] && s[1] == s[2] {"Won"} else {"Lost"});
}
1
2
3
4
5
6
7
8
9

# Problem B - Alcoholic (opens new window)

因为Takahashi按顺序喝酒,所以我们直接模拟喝酒过程,在每一杯喝完之后检查是否醉酒即可。

  • 时间复杂度O(N)\mathcal{O}(N)
  • 空间复杂度O(1)\mathcal{O}(1)
参考代码 (Rust)
use proconio::input;

fn main() {
    input! {
        n: usize,
        mut x: usize,
        liquor: [(usize, usize); n],
    }

    x *= 100;
    let mut tot = 0;
    for i in 0..n {
        tot += liquor[i].0 * liquor[i].1;
        if tot > x {
            println!("{}", i + 1);
            return;
        }
    }
    println!("-1");
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# Problem C - Mandarin Orange (opens new window)

这是一道经典题,实际上跟LC84 - 柱状图中最大的矩形 (opens new window)是一样的。我们可以用单调栈解决本题。

在栈中,我们存储高度值和它们对应的起始位置。栈是升序的,也即最大的元素在栈顶。

现在考虑位置ii,除非栈已经为空,或者栈顶元素的高度值小于当前高度,我们一直将栈顶元素出栈。在这一过程中,我们不断更新答案(每一个被弹出的高度值最远可以延伸到i1i-1处),同时记录下最左的位置。接下来我们把当前的高度和最左的位置一起入栈。

为了避免单独处理边界情况,我们在数组最后加上一个00

  • 时间复杂度O(N)\mathcal{O}(N).
  • 空间复杂度O(N)\mathcal{O}(N).
参考代码 (Rust)
use proconio::input;
use std::collections::VecDeque;

fn main() {
    input! {
        n: usize,
        mut a: [usize; n],
    }

    a.push(0);
    let mut ans = 0;
    let mut dq: VecDeque<(usize, usize)> = VecDeque::new();
    for i in 0..=n {
        let mut nl = i;
        while !dq.is_empty() && dq.back().unwrap().0 > a[i] {
            let (h, l) = *dq.back().unwrap();
            ans = ans.max(h * (i - l));
            dq.pop_back();
            nl = l;
        }
        if dq.is_empty() || dq.back().unwrap().0 < a[i] {
            dq.push_back((a[i], nl));
        }
    }
    println!("{}", ans);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# Problem D - Logical Expression (opens new window)

一道非常简单的动态规划。我们只需要保存两个数,使得当前结果为false的方法数ff,以及使得当前结果为true的方法数tt,转移如下:

(f,t)={(2f+t,t)si=AND(f,2t+f)si=OR(f,t)=\left\{\begin{aligned} & (2f+t,t) & s_i=\text{AND}\\ & (f, 2t+f) & s_i=\text{OR}\end{aligned}\right.

  • 时间复杂度O(N)\mathcal{O}(N).
  • 空间复杂度O(1)\mathcal{O}(1).
参考代码(Rust)
use proconio::input;

fn main() {
    input! {
        n: usize,
        s: [String; n],
    }

    let mut f = 1u64;
    let mut t = 1u64;

    for si in s {
        if si == "AND" {
            let nf = f * 2 + t;
            let nt = t;
            f = nf;
            t = nt;
        } else {
            let nf = f;
            let nt = t * 2 + f;
            f = nf;
            t = nt;
        }
    }

    println!("{}", t);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

# Problem E - Rotate and Flip (opens new window)

很自然想到首先对所有询问按照时间点进行排序,然后离线处理。

下面,我们来看看四种操作的结果。对于点(x,y)(x,y)

  • 操作一的结果为(y,x)(y,-x)
  • 操作二的结果为(y,x)(-y, x)
  • 操作三的结果为(2px,y)(2p-x,y)
  • 操作四的结果为(x,2py)(x,2p-y)

因为每种操作都是对所有点同时进行的,我们不会去逐个计算每个点的变化,而是计算得到最后的变换规则。

操作一和二交换了xxyy,同时改变了其中某一个轴的符号。操作三和四翻转了某一个轴,然后再加上2p2p。我们使用五个变量来记录这些操作的合成效果。swapswap表示xxyy是否被交换,sxs_x表示当前xx轴的符号,sys_y表示当前yy轴的符号,cxc_x表示xx轴的额外变化量,cyc_y表示yy轴的额外变化量。

对于四种操作:

  • 操作一:swapswap被翻转,cxc_xcyc_y被交换,sxs_xsys_y被交换,之后sys_ycyc_y被翻转。
  • 操作二:swapswap被翻转,cxc_xcyc_y被交换,sxs_xsys_y被交换,之后sxs_xcxc_x被翻转。
  • 操作三:sxs_x被翻转,cxc_x翻转后加上2p2p
  • 操作四:sys_y被翻转,cyc_y翻转后加上2p2p

对于每一询问,我们首先进行操作直到操作次数满足要求。接下来,我们利用上面的五个变量计算最终的点的位置:

  • 如果swapswapfalse,则结果为(sxx0+cx,syy0+cy)(s_xx_0+c_x,s_yy_0+c_y)

  • 如果swapswaptrue,则结果为(sxy0+cx,syx0+cy)(s_xy_0+c_x,s_yx_0+c_y)

  • 时间复杂度O(QlogQ)\mathcal{O}(Q\log Q)

  • 空间复杂度O(Q)\mathcal{O}(Q)

参考代码 (Rust)
use proconio::input;
use proconio::marker::Usize1;
use std::cmp::Ordering;

fn main() {
    input! {
        n: usize,
        points: [(i64, i64); n],
        m: usize,
    }

    let mut ops: Vec<(usize, i64)> = vec![];
    for _i in 0..m {
        input! {
            t: usize,
        }
        if t <= 2 {
            ops.push((t, 0));
        } else {
            input! {
                p: i64,
            }
            ops.push((t, p));
        }
    }

    input! {
        q: usize,
        queries: [(usize, Usize1); q],
    }

    let mut ans = vec![(0i64, 0i64); q];
    let mut order: Vec<usize> = (0..q).collect();
    order.sort_by(|a, b| if queries[*a].0 <= queries[*b].0 {
        Ordering::Less
    } else {
        Ordering::Greater
    });

    let mut swap = false;
    let mut sx = 1i64;
    let mut sy = 1i64;
    let mut cx = 0i64;
    let mut cy = 0i64;
    let mut op = 0usize;
    for i in order {
        let (a, b) = queries[i];
        while op < a {
            let (t, p) = ops[op];
            match t {
                1 => {
                    swap = !swap;
                    std::mem::swap(&mut cx, &mut cy);
                    std::mem::swap(&mut sx, &mut sy);
                    sy = -sy;
                    cy = -cy;
                },
                2 => {
                    swap = !swap;
                    std::mem::swap(&mut cx, &mut cy);
                    std::mem::swap(&mut sx, &mut sy);
                    sx = -sx;
                    cx = -cx;
                },
                3 => {
                    sx = -sx;
                    cx = p * 2 - cx;
                },
                4 => {
                    sy = -sy;
                    cy = p * 2 - cy;
                },
                _ => {

                }
            }
            op += 1;
        }
        if swap {
            ans[i] = (points[b].1 * sx + cx, points[b].0 * sy + cy);
        } else {
            ans[i] = (points[b].0 * sx + cx, points[b].1 * sy + cy);
        }
    }

    for i in 0..q {
        println!("{} {}", ans[i].0, ans[i].1);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89

# Problem F - Sugoroku2 (opens new window)

我们首先考虑下面这个简化的问题:

  • 我们有一项任务,成功的概率为psp_s,成功的花费为EsE_s;失败的概率为pf=1psp_f=1-p_s,花费为EfE_f
  • 如果成功了,我们即刻停止;否则我们将再次尝试。
  • 最后的总花费的期望是多少?

这个期望可以表达为:

E=Esps+(Ef+Es)pfps+(2Ef+Es)pf2ps+E=E_sp_s+(E_f+E_s)p_fp_s+(2E_f+E_s)p_f^2p_s+\cdots

从而:

Esps+pfps((Ef+Es)+(2Ef+Es)pf+)E_sp_s+p_fp_s((E_f+E_s)+(2E_f+E_s)p_f+\cdots)

为此,我们需要计算

s=Es+Espf+Espf2+=Es11pf=Esps\sum_s=E_s+E_sp_f+E_sp_f^2+\cdots=E_s\frac{1}{1-p_f}=\frac{E_s}{p_s}

f=Ef+2Efpf+3Efpf2+\sum_f=E_f+2E_fp_f+3E_fp_f^2+\cdots

后者的计算相对困难一些。

事实上,我们有:

fpf=Efpf+2Efpf2+\sum_fp_f=E_fp_f+2E_fp_f^2+\cdots

从而:

(1pf)f=Ef(1+pf+pf2+)=Ef11pf=Efps(1-p_f)\sum_f=E_f(1+p_f+p_f^2+\cdots)=E_f\frac{1}{1-p_f}=\frac{E_f}{p_s}

所以:

f=Efps2\sum_f=\frac{E_f}{p_s^2}

接下来我们将这两个和代入原式,就可以得到:

E=Esps+pfps(f+s)=Esps+pfps(Esps+Efps2)=Esps+(1ps)(Es+Efps)E=E_sp_s+p_fp_s(\sum_f+\sum_s)=E_sp_s+p_fp_s(\frac{E_s}{p_s}+\frac{E_f}{p_s^2})=E_sp_s+(1-p_s)(E_s+\frac{E_f}{p_s})

接下来我们回到原来的问题,想办法计算EsE_sEfE_f以及psp_s

这里,如果我们到达了第NN个位置,我们就成功了;如果我们被送回了00位置,我们就失败了。

求解的方法与 LC837 - 新21点 (opens new window)类似。我们利用所有能够到达当前位置的位置的概率之和来计算到达当前位置的概率:

pi=pMp_i=\frac{\sum_p}{M}

当然,损坏的格子不会对p\sum_p作出贡献。同时,我们需要在计算完第ii个位置之后将pip_i加入p\sum_p(如果ii没有损坏的话),同时将piMp_{i-M}减去(如果iMi\geq M且第iMi-M个格子没有损坏)。

但除了概率,我们还需要计算期望EiE_i,它可以表示为:

Ei=pj(Ej+1)p=1+pjEjpE_i=\frac{\sum p_j(E_j+1)}{\sum_p}=1+\frac{\sum p_jE_j}{\sum_p}

这个表达式提示我们计算piEip_iE_i而非EiE_i

piEi=pM+pjEjMp_iE_i=\frac{\sum_p}{M}+\frac{\sum p_jE_j}{M}

因此,我们可以用和计算pip_i类似的方法来计算piEip_iE_i。注意损坏的格子不会对pE\sum_{pE}作出贡献,但是会对pfEfp_fE_f作出贡献。

因为超过第NN个格子也被视为到达第NN个格子,因此我们的循环会在第N1N-1个格子处停止。对于每一个能够从其到达第NN个格子的位置,我们根据比例i+MN+1M\frac{i+M-N+1}{M}计算其对pNp_NpNENp_NE_N的贡献。

最后,我们就得到了pfEfp_fE_fps=pNp_s=p_N以及psEs=pNENp_sE_s=p_NE_N,利用这些值和前面推导得到的公式,我们可以计算出最终的答案EE

  • 时间复杂度O(N)\mathcal{O}(N)
  • 空间复杂度O(N)\mathcal{O}(N)
参考代码 (Rust)
use proconio::input;

fn main() {
    input! {
        n: usize,
        m: usize,
        k: usize,
        a: [usize; k],
    }

    let mut broken = vec![false; n + 1];
    for i in a {
        broken[i] = true;
    }

    let mut prob_exp = vec![0.0; n + 1];
    let mut prob = vec![0.0; n + 1];
    prob[0] = 1.0;

    let mut prob_sum: f64 = 1.0;
    let mut exp_sum: f64 = 0.0;

    if m >= n {
        let goal_pct = (m - n + 1) as f64 / m as f64;
        prob[n] += prob[0] * goal_pct;
        prob_exp[n] += (prob_exp[0] + prob[0]) * goal_pct;
    }

    let mut prob_exp_fail = 0.0;

    for i in 1..n {
        prob[i] = prob_sum / m as f64;
        prob_exp[i] = prob[i] + exp_sum / m as f64;
        if !broken[i] {
            prob_sum += prob[i];
            exp_sum += prob_exp[i];
            if i + m >= n {
                let goal_pct = (i + m - n + 1) as f64 / m as f64;
                prob[n] += prob[i] * goal_pct;
                prob_exp[n] += (prob_exp[i] + prob[i]) * goal_pct;
            }
        } else {
            prob_exp_fail += prob_exp[i];
        }
        if i >= m && !broken[i - m] {
            prob_sum -= prob[i - m];
            exp_sum -= prob_exp[i - m];
        }
    }

    if prob[n] < 1e-8 {
        println!("-1");
    } else if (prob[n] - 1.0).abs() < 1e-8 {
        println!("{}", prob_exp[n]);
    } else {
        let exp_fail = prob_exp_fail / (1.0 - prob[n]);
        println!("{}", prob_exp[n] + (1.0 - prob[n]) * (prob_exp[n] + exp_fail) / prob[n]);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59