CF908D New Year and Arbitrary Arrangement 题解

Altwilio

2022-04-13 16:43:38

题解

upd 2024.2.18 修改笔误

0. 前言

有一天 Au 爷讲期望见到了此题,通过写题解来加深理解。

1. 题意

将初始为空的序列的末尾给定概率添加 ab,当至少有 kab 时停止(注意是“对”,中间可以间隔字符),求 ab 期望对数。

2. 思路

通过查看标签 通过阅读题面我们容易发现本题是一道期望 DP,但是本题的状态并不很容易想到,设 f[i][j] 表示前缀中有 iajab 停止后的期望个数,这样发现转移就容易了很多,不会被 ab 纠缠不清,设 A = pa / (pa + pb)B = pb / (pa + pb),则有:

f[i][j] = A × f[i + 1][j] + B × f[i][j + i]

i + j ⩾ k,则再加一个 b 就会结束,此时的期望 ab 数是:

i + j + pa / pb

故终止状态为:

f[i][j] = i + j + pa / pb, i + j ⩾ k

3. 解释

(本块主要针对 i + j + pa / pb 的推导,不感兴趣可以跳过)

我一直疑惑 i + j + pa / pb 如何得出。

解释一下,在前缀有了 ia,构成了 jab 的情况下,若 i + j ⩾ k,这个状态的后继情况能是容易看到的:选 a ,然后继续抉择,或选 b ,就此停止。多选一个 a,就意味着最后的 ab 串又多了一个。 那么得出无限和式:

{b \over pa+pb}×\sum_{a=0}^{∞}(i+j+a)×({pa \over pa+pb})^{a}

接下来的证明部分参考一粒夸克的博客

首先是等差乘等比数列求和公式

(1):A=a+(a+p)×p+(a+2×b)×p^2+...+(a+n×b)×p^n (2):A×p=a×p+(a+b)×p^2+(a+2×b)×p^3+...+(a+n×b)×p^{n+1} (1)-(2):A×(1-p)=a+b×(p+p^2+p^3+...+p^n)-(a+n×b)p^{n+1} A×(1-p)=a+b×p×{1-p^n \over 1-p}-(a+n×b)×p^{n+1} A={a\over1-p}+b×{p-p^{n+1}\over(1-p)^2}-{(a+n×b)×p^{n+1}\over1-p}

将公式代入无限和式

{pb \over pa+pb}×(\sum_{a=0}^{∞}(i+j+a)×({pa \over pa+pb})^{a}) ={pb\over pa+pb}×({i+j\over1-{pa\over pa+pb}}+{{pa \over pa+pb}-({pa \over pa+pb})^{∞+ 1} \over (1-{pa \over pa+pb})^2}-{(i+j+n)×({pa \over pa+pb})^{∞+1} \over1-{pa \over pa+pb}}) ={pb\over pa+pb}×({i+j \over {pb \over pa+pb}}+{{pa \over pa+pb}-({pa \over pa+pb})^{∞+ 1} \over ({pb \over pa+pb})^2}-{(i+j+n)×({pa \over pa+pb})^{∞+1} \over{pa \over pa+pb}}) =i+j+{{pa \over pa+pb}-({pa \over pa+pb})^{∞+ 1} \over {pb \over pa+pb}}-(i+j+n)×({pa \over pa+pb})^{∞+ 1} =i+j+{{pa \over pa+pb}\over {pb \over pa+pb}} =i+j+{pa \over pb}

(这么巨量\LaTeX我都打了,求赞)

4. 细节

  1. 由于 f[0][0] 会转移到自己,递归记忆化会死循环,从 f[1][0] 开始算,当序列前有一堆 b 的情况没有意义,可以跳到第一个 a 发生时开始算。初始状态选取 f[1][0]
  2. aab 的个数相加已经大于 k 了,这时就不关心有多少 a 了,只需要有一个 b 就可以结束了,这样可以把两维都控制在 O(k) 的复杂度

5. 代码

这是一份逆推实现的代码:

#include<map>
#include<cmath>
#include<queue>
#include<vector>
#include<cstdio>
#include<string>
#include<cstring>
#include<iostream>
#include<algorithm>
#define int long long
using namespace std;

template<class T> inline void read(T &x){
    x = 0; register char c = getchar(); register bool f = 0;
    while(!isdigit(c)) f ^= c == '-', c = getchar();
    while(isdigit(c)) x = x * 10 + c - '0', c = getchar();
    if(f) x = -x;
}

template<class T> inline void print(T x){
    if(x < 0) putchar('-'), x = -x;
    if(x > 9) print(x / 10);
    putchar('0' + x % 10);
}

const int N = 1010;
const int mod = 1e9 + 7;
int n, pa, pb, A, B, C;
int f[N][N];

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

inline int work(int x){
    return qpow(x, mod - 2);
}

signed main(){
    read(n), read(pa), read(pb);
    A = 1ll * pa * work(pa + pb) % mod;
    B = 1ll * pb * work(pa + pb) % mod;
    C = 1ll * pa * work(pb) % mod;
    for(int i = n; i >= 1; --i)
        for(int j = n; j >= 0; --j){
            if(i + j >= n) f[i][j] = (i + j + C) % mod;
            else f[i][j] = (1ll * A * f[i + 1][j] % mod + 1ll * B * f[i][j + i] % mod) % mod;
        }
    print(f[1][0]), puts("");
    return 0;
}

这是一份记搜实现的代码片段:

inline int dp(int i, int j){
    if(i + j >= k) return (i + j + C) % mod;
    if(~ f[i][j]) return f[i][j];
    return (1ll * A * dp(i + 1, j) + 1ll * B * dp(i, j + i)) % mod;
}