萌新の概率与期望

不说闲话

概率和期望一直是自己非常薄弱的板块,最早学的时候其实就没有完全听懂。

导致打模拟赛,甚至是 ABC 的时候遇到概率期望相关的题基本上都是绕道走,有时候暴力都打不出来。

重修一下概率论,接下来是做题笔记,后面也会整理成讲题。

P1365 WJMZBMR打osu! / Easy

期望入门题。

题目大意

有一个长度为 \(N\) 的字符串,由 ox? 组成。

定义总分数为:字符串中每一段最长连续 o 字串长度的平方和。

? 的含义是该字符不确定,是 ox,各有 \(50 \%\) 的可能性。

求总分数的期望值。

解题思路

很容易想到 dp。

  • \(f_i\) 表示前 \(i\) 个字符分数的期望值;
  • \(g_i\) 表示前 \(i\) 个字符中以 \(i\) 为最后一个字符的连续 o 字串的期望长度。

接下来就可以分类讨论进行状态转移:

\(\begin{cases} s_i=o \begin{cases} f_i= f_{i-1}+ 2\times g_{i-1} +1 \\ g_i=g_{i-1}+1 \end{cases}\\ \\ s_i=x \begin{cases} f_i= f_{i-1} \\ g_i=0 \\ \end{cases}\\ \\ s_i=?\ \ \begin{cases} f_i=f_{i-1}+g_{i-1}+0.5 \\ g_i=0.5\times g_{i-1} + 0.5 \end{cases}\\ \end{cases}\)

时间复杂度 \(O(N)\)

P4927 [1007] 梦美与线段树

恶心题,式子并不难推,但是代码有一点繁琐。

题目大意

有一颗线段树,每次按节点权值的占比的概率进入该子树,求走过的权值和的期望值。

解题思路

令当前节点为 \(rt\),则不难看出进入 \(rt\) 左子树的期望为:

\(sum_l \times P(l) = sum_l \times \frac {sum_l}{sum_{rt}} = \frac {sum_l^2}{sum_{rt}}\)

同理,进入 \(rt\) 右子树的期望为:

\(sum_r \times P(r) = sum_r \times \frac {sum_r}{sum_{rt}} = \frac {sum_r^2}{sum_{rt}}\)

那么就可以得出 \(rt\) 节点走过的权值和的期望值为:\(\frac {sum_l^2+sum_r^2}{sum_{rt}}\)

下半部分是很好维护的,仅需要维护区间和的线段树即可,而不难看出上半部分就是一个维护区间平方和。

怎么维护呢?考虑每次更新,设增加的数值为 \(\Delta V\)

那么 \(rt\) 节点的平方和就成为了 \((sum_{rt} + len_{rt} \times \Delta V)^2\)

考虑完全平方公式展开:

$(sum_{rt} + len_{rt} \times \Delta V)^2 = sum_{rt}^2 + 2 \times len_{rt} \times sum_{rt} \times \Delta V + len_{rt}^2 \times {\Delta V}^2 $。

\(len\)\({len}^2\) 都可以在线段树的 build 阶段维护。

那么我们只需要知道如何维护 \(len_{rt} \times sum_{rt}\),这道题就做完了。

每次更新,\(len_rt \times sum_{rt}\) 就会变成 \(len_{rt} \times (sum_{rt} + len_{rt} \times \Delta V)\)

再拆开来:

\(len_{rt} \times sum_{rt} + len_{rt}^2 \times \Delta V\)

所以每次更新,\(len_{rt} \times sum_{rt}\) 只需要加上 \(len_{rt}^2 \times \Delta V\) 即可, \(len^2\) 前边已经说过如何维护了。

乍一看,这棵线段树要维护的东西似乎有点多,比较繁琐。

线段树部分代码:

struct Sgt_Tree{
    #define lson rt<<1,l,md
    #define rson rt<<1|1,md+1,r

    int sum[N<<2],sum_2[N<<2];
    int len[N<<2],len_2[N<<2]; 
    int len_sum[N<<2],tag[N<<2];

    void push_up(int rt,int l,int r){
        sum[rt]=sum[rt<<1]+sum[rt<<1|1];
        sum_2[rt]=sum_2[rt<<1]+sum_2[rt<<1|1]+sum[rt]*sum[rt];
        len_sum[rt]=len_sum[rt<<1]+len_sum[rt<<1|1]+(r-l+1)*sum[rt];
    }
    void build(int rt,int l,int r){
        if(l==r){
            sum[rt]=a[l];
            len[rt]=1,len_2[rt]=1;
            sum_2[rt]=sum[rt]*sum[rt];
            tag[rt]=0;
            len_sum[rt]=sum[rt];
            return;
        }

        int md=(l+r)>>1;
        build(lson);build(rson);
        len[rt]=(len[rt<<1]+len[rt<<1|1]+(r-l+1))%mod;
        len_2[rt]=(len_2[rt<<1]+len_2[rt<<1|1]+(r-l+1)*(r-l+1))%mod;
        push_up(rt,l,r);
    }
    
    void make_tag(int rt,int len,int x){
        tag[rt]+=x;
        sum_2[rt]=sum_2[rt]+2*len_sum[rt]*x;
        sum_2[rt]=sum_2[rt]+len_2[rt]*x*x;
        len_sum[rt]+=len_2[rt]*x;
        sum[rt]+=len*x;  
    }
    void push_down(int rt,int l,int r){
        if(tag[rt]){
            int md=(l+r)>>1;
            make_tag(rt<<1,md-l+1,tag[rt]);
            make_tag(rt<<1|1,r-md,tag[rt]);
        }
        tag[rt]=0;
    }
    void update(int rt,int l,int r,int L,int R,int x){
        if(L<=l&&r<=R){
            make_tag(rt,r-l+1,x);
            return ;
        }

        push_down(rt,l,r);

        int md=(l+r)>>1;        
        if(L<=md)update(lson,L,R,x);
        if(R>md)update(rson,L,R,x);
        
        push_up(rt,l,r);
    }

}Tr;

CF749E Inversions After Shuffle

题目大意

给定一个排列,随机打乱其中一部分,求期望的逆序对个数。

解题思路

考虑对于一个数对 \((a_i,a_j),i<j\)

如果 \((a_i,a_j)\) 被包含在打乱的区间中,则会产生一半的正贡献或一半的负贡献。

\((a_i,a_j)\) 被包含在打乱的区间中的概率为:\(\frac{i\times (n-j+1)}{\frac{1}{2}\times n\times (n+1)} = \frac{2\times i \times (n-j+1)}{n\times (n+1)}\)

那么:

\(a_i>a_j\),则减少的逆序对个数增加 \(\frac{1}{2}\times \frac{2\times i\times (n-j+1)}{n\times (n+1)} = \frac{i\times (n-j+1)}{n\times (n+1)}\)

同理,\(a_i<a_j\),则减少的逆序对个数减少 \(\frac{1}{2}\times \frac{2\times i\times (n-j+1)}{n\times (n+1)} = \frac{i\times (n-j+1)}{n\times (n+1)}\)

可以用树状数组维护,建两颗树状数组,一颗计算原序列逆序对个数,另一颗维护上述的减少逆序对的个数,二者相减即可。

这里给出核心代码(树状数组略):

void solve(){
    int n;
    cin>>n;

    vector<int> a(n+1);
    for(int i=1;i<=n;i++){
        cin>>a[i];
    }

    Tr1.init(n),Tr2.init(n);

    double cnt=0,ans=0;
    for(int i=1;i<=n;i++){
        cnt+=Tr1(n)-Tr1(a[i]);
        Tr1.add(a[i],1);
    }

    for(int i=1;i<=n;i++){
        ans+=(n-i+1)*(Tr2(n)-Tr2(a[i]));
        ans-=(n-i+1)*Tr2(a[i]);
        Tr2.add(a[i],i);
    }
    ans/=n*(n+1);

    cout<<fixed<<setprecision(12)<<cnt-ans<<endl;
}

P3317 [SDOI2014] 重建

概率和线性代数的结合。

题目大意

一个图中每条边都有一定概率出现,问生成的图恰好是一颗树的概率。

解题思路

题目要求 \(ans = \sum _{T} \ \ [\ \ \prod _{e \in T} p_e \times \prod _{e \notin T} (1-p_e) \ \ ]\)

根据矩阵树定理:

定义基尔霍夫矩阵为:无向图的邻接矩阵减去度数矩阵。

那么生成树的个数即为基尔霍夫矩阵的任意 \(k-1\) 阶主子式。

本题求得是概率,那么我们可以简单推广一下:

\(|\text K| = \sum _{T} \prod _{e \in T} p_e\)

但是注意到矩阵树定理只能求生成图至少是一颗树的概率,怎样才能求出 $\sum _T \prod _{e\notin T (1-p_e)} $ 呢?

考虑:

\(\sum _T \prod _{e\notin T } (1-p_e) = \sum _T \frac {\prod _e (1-p_e)} {\prod _{e\in T} (1-p_e)}\)

\(ans= \prod _e (1-p_e) \times \sum_T \prod _{e \in T} \frac {p_e}{1-p_e}\)

我们直接把无向图的邻接矩阵的边权设为 \(\frac {p_e}{1-p_e}\),那么 \(\sum_T \prod _{e \in T} \frac {p_e}{1-p_e}\) 就可以用矩阵树定理,求 \(n-1\) 阶主子式,时间复杂度 \(O(N^3)\),前半部分可以边读入边计算,最后乘起来,这道题就做完了。

写的时候需要注意精度问题,每次读进概率后加上 \(10^{-8}\)\(eps\)

posted @ 2025-01-16 21:39  SunburstFan1106  阅读(18)  评论(0)    收藏  举报