luoguP3600 随机数生成器 期望概率DP + DP优化

这篇题解更像对他人题解的吐槽和补充?

 

考虑答案

$E[X] = \sum\limits_{i = 1}^{x} i P(X = i)$

$P(X = i)$不好求................(其实完全可以求的......而且求法和下面的方法蜜汁相似......)

那么,我们考虑整数概率公式(既然$P(X = i)$能求,那这公式到底有什么用?)

$E[X] = \sum\limits_{i = 1}^{x} P(x \geq i)$

当然,你也可以选择求$E[X] = \sum\limits_{i = 1}^{x} i * (P(x \geq i) - P(x \geq i + 1))$

或者求$E[X] = \sum\limits_{i = 1}^{x} i * (P(x \leq i + 1) - P(x \leq i))$

不过方法没什么本质的区别..........

 

那么,考虑求枚举$x$后求$P(x \geq i)$,由于题目是最大值,这是“或”概率,不好求

因此考虑“非与非”,即求反面$1 - P(x \leq i - 1)$

 

相信你能发现,被包含的区间的答案是无所谓的...

因此,将区间去包含就成了随着左端点递增,右端点递增的局面

接着,考虑一个点能让哪些区间得出正确的结果然后转移

不妨设一个点能影响的区间为$[L[i], R[i]]$,记为$S[i]$

令$f[i]$表示让第$i$个小于$x$,并且$1 ... R[i]$的所有询问都合法的概率

为了方便书写,令随机出小于等于$v$的数的概率为$p$,那么$p = \frac{v}{x}$

那么,我们枚举跟$i$的区间有交的$j$,然后让$[i + 1, j - 1]$强行大于$v$转移

(注意,需要认为存在一段$[0, 0]$的区间,并且$0$已经选择了小于等于$v$的数来辅助转移,因此开始要特判...)

$f[i] = (p * \sum\limits_{S[i] \cap S[j] \neq \varnothing} f[j] * (1 - p)^{i - j - 1})$

用$two - pointer$和前缀和可以优化到$O(n)$

(只要去掉开头的$p$就是求$P(x = i)$了,但是这么做在有点没被区间覆盖时不能采用下面的算法)

(需要单独把没有被区间覆盖的点拿出来,不让他们参与转移...毕竟$p + 1 - p = 1$,但是$1 - p \neq 1$,这么写略麻烦)

 

那如果有些点完全没被区间覆盖怎么办呢?

不妨设$[l_1, r_1], [l_2, r_2]$编号为$i, j$,且有$l_1 \leq r_1 < l_2 \leq r_2$

那么对于$[r_1 + 1, l_2 - 1]$中的点,令其$S = [j, i]$(没写错)

这时,$i$能顺利地转移到$S$一次,同时把$S$和右边的第一块没有被两个区间交的部分绑在了一起转移....

(这可以保证增加区间$[0, 0]$和区间$[n + 1, n+1]$的正确性)

(然而出题人并没有谈,所以数据保证所有的点都被区间覆盖?)

 

最后求$P(x < i)$的时候,相当于还单独存在一段$[n + 1, n + 1]$的区间,并且$n + 1$已经选择了小于$i$的数...

可以选择加上这段区间或者最后特判下....

复杂度$O(nx)$

注:$luogu$最近是不是慢了....

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;

extern inline char gc() {
    static char RR[23456], *S = RR + 23333, *T = RR + 23333;
    if(S == T) fread(RR, 1, 23333, stdin), S = RR;
    return *S ++;
}
inline int read() {
    int p = 0, w = 1; char c = gc();
    while(c > '9' || c < '0') { if(c == '-') w = -1; c = gc(); }
    while(c >= '0' && c <= '9') p = p * 10 + c - '0', c = gc();
    return p * w;
}

#define ri register int
#define sid 2005
const int mod = 666623333;

int n, x, q, ans;
int ps[sid], snp;
int L[sid], R[sid], f[sid], inv[sid], fac[sid];
struct seg { 
    int l, r; 
    friend bool operator < (seg a, seg b) 
    { return a.l < b.l || (a.l == b.l && a.r > b.r); }
} s[sid];

int fp(int a, int k) {
    int ret = 1;
    for( ; k; k >>= 1, a = 1ll * a * a % mod)
    if(k & 1) ret = 1ll * ret * a % mod;
    return ret;
}

int main() {
    n = read(); x = read(); q = read();
    for(ri i = 1; i <= q; i ++) {
        int l = read(), r = read();
        s[i].l = l; s[i]. r = r;
    }
    
    sort(s + 1, s + q + 1);
    for(ri i = 1; i <= q; i ++) {
        while(s[i].r <= s[ps[snp]].r) snp --;
        ps[++ snp] = i;
    }
    q = snp; for(ri i = 1; i <= q; i ++) s[i] = s[ps[i]];

    int fr = 1, to = 0;
    for(ri i = 1; i <= n; i ++) {
        while(to < q && s[to + 1].l <= i) to ++;
        while(fr <= to && s[fr].r < i) fr ++;
        L[i] = fr; R[i] = to;
    }

    for(ri v = 1; v <= x; v ++) {
        int p = 1ll * (v - 1) * fp(x, mod - 2) % mod, bp = (1 - p + mod) % mod;
        int pc = fp(bp, mod - 2), sum = 1;
        inv[0] = fac[0] = f[0] = 1;
        for(ri i = 1; i <= n; i ++) {
            inv[i] = 1ll * inv[i - 1] * pc % mod;
            fac[i] = 1ll * fac[i - 1] * bp % mod;
        }
        for(ri i = 1, j = 0; i <= n; i ++) {
            while(j < i && R[j] < L[i] - 1) ((sum -= 1ll * f[j] * inv[j] % mod) += mod) %= mod, j ++;
            f[i] = 1ll * sum * fac[i - 1] % mod * p % mod;
            (sum += 1ll * f[i] * inv[i] % mod) %= mod; 
        }
        int anp = 0;
        for(ri i = 1; i <= n; i ++)
        if(R[i] == q) (anp += 1ll * f[i] * fac[n - i] % mod) %= mod;
        (ans += (1 - anp + mod) % mod) %= mod; 
    }
    printf("%d\n", ans);
    return 0;
}

 

posted @ 2018-08-22 00:55  remoon  阅读(319)  评论(0编辑  收藏  举报