概率期望 / dp

正整数随机变量 x x x 的期望值: E ( x ) = ∑ i = 1 ∞ P ( x > = i ) E(x)=\sum_{i=1}^{\infty} P(x>=i) E(x)=i=1P(x>=i)

Blocks
期望dp,从已经满足的点倒着推,首先考虑状态,发现 n n n很小,直接状压,然后暴力枚举状态看是否全部覆盖,发现坐标跨度很大,对坐标离散化,依次差分修改, O ( n 2 2 n ) O(n^22^n) O(n22n),然后就可以直接dp了
d p i = ∑ j d p i [ ( 1 < < j ) & i ] + 1 + ∑ j d p i ∣ j + 1 [ ( 1 < < j ) & 1 = = 0 ] n dp_i={{\sum_j dp_i [(1<<j)\&i]+1+ \sum_j dp_{i|j}+1 [(1<<j)\&1==0] }\over n} dpi=njdpi[(1<<j)&i]+1+jdpij+1[(1<<j)&1==0]
化简得: d p i = n + ∑ j d p i ∣ j [ ( 1 < < j ) & 1 = = 0 ] n − c n t 1 ( i ) dp_i={ n+\sum_j dp_{i|j} [(1<<j)\&1==0] \over n-cnt1(i)} dpi=ncnt1(i)n+jdpij[(1<<j)&1==0]

P3239 [HNOI2015] 亚瑟王
一张卡牌一整局只会产生一次贡献,不妨直接考求出这一次贡献所产生的的概率为多少。

g i g_i gi表示第 i i i张牌在 r r r轮中发动一次技能的概率,发现这个东西不太好找前后的递推关系。我们发现当前卡牌是否发动技能的概率受到前面卡牌的影响(前面选了几张卡,那么我再怎么也要有几轮把前面的卡选完),但不受到后面卡牌的影响,所以我们设 f i , j f_{i,j} fi,j,表示在这 r r r轮中,前 i i i张牌有 j j j张牌被选的概率,这样我们就知道有多少轮能在 i i i这个地方做出决策(产生被选的概率),从而算出 g i g_i gi

然后 f i , j f_{i,j} fi,j每次只要看在 r r r轮中 i i i这张卡牌是否产生发动一次技能,分类转移

    std::vector<std::vector<double>> f(n+1,std::vector<double>(r+1));
    f[0][0]=1;
    for (int i=1;i<=n;i++){
        for (int j=0;j<=r;j++){
            f[i][j]=f[i-1][j]*ksm(1-p[i],r-j);
            if (j) f[i][j]+=f[i-1][j-1]*(1-ksm(1-p[i],r-j+1));
        }
    }

    std::vector<double> g(n+1);
    double ans=0;
    for (int i=1;i<=n;i++){
        for (int j=0;j<=r;j++){
            g[i]+=f[i-1][j]*(1-ksm(1-p[i],r-j));
        }
        ans+=g[i]*d[i];
    }
    std::cout<<std::fixed<<std::setprecision(10)<<ans<<"\n";

P3750 [六省联考 2017] 分手是祝愿
50%的数据 k = = n k==n k==n,也就是说只要找到最小的关灯次数
直接从后往前遍历,当前点为 i i i,如果现在 i i i这个位置为1,那只能在这按一次开关,然后 O ( n ) O(\sqrt n) O(n )去修改其它位置的状态。

考虑有等概率随机按开关的情况,其实在前面我们就能知道,给出的数据我们一共要按的开关就只有那几个,一个开关重复按两次是没有效果的,按错了开关到最后也得按回去,所以我们状态设计就是已经按了几个要按的开关, f i f_i fi表示已经按了 i i i个要按的开关的期望。
i < = k , f i = k i<=k,f_i=k i<=k,fi=k
i > k i>k i>k 我们分类按对了开关或按错了开关:
f i = i n ⋅ f i − 1 + n − i n ⋅ f i + 1 f_i={i \over n}\cdot f_{i-1}+{n-i \over n} \cdot f_{i+1} fi=nifi1+nnifi+1
f i = f i − 1 + b i f_i=f_{i-1}+b_i fi=fi1+bi b i b_i bi代入式子中解出 b i b_i bi的递推式,然后根据推出 f i f_i fi

另一种状态设计就是 f i f_i fi表示剩下 i i i个开关要按,到剩下 i − 1 i-1 i1个开关要按的期望为多少
f i = i n + n − i n ( 1 + f i + 1 + f i ) f_i={i \over n}+{n-i \over n}(1+f_{i+1}+f_{i}) fi=ni+nni(1+fi+1+fi) 然后移项即可得到递推式
最后累加

代码是第一种方法

    std::vector<ll> b(n+1);
    b[n]=1;
    int cnt=0;
    for (int i=n;i>=1;i--){
        if (a[i]){
            cnt++;
            for (int j=1;j*j<=i;j++){
                if (i%j==0){
                    a[j]^=1;
                    if (i/j!=j) a[i/j]^=1;
                }
            }
        }

        if (i<n){
            b[i]=(n-i+mod)%mod*b[i+1]%mod*ni(i)%mod+n*ni(i)%mod;
            b[i]%=mod;
        }
    }
    std::vector<ll> f(n+1);
    for (int i=1;i<=k;i++){
        f[i]=i%mod;
    }
    for (int i=k+1;i<=cnt;i++){
        f[i]=(f[i-1]+b[i])%mod;
    }
    ll ans=f[cnt];
    for (int i=1;i<=n;i++){
        ans=ans*i%mod;
    }
    std::cout<<ans;

P3412 仓鼠找sugar II
首先考虑固定终点,令终点为根节点, f i f_i fi表示从 i i i到其父亲节点的期望步数,对于叶子结点 f i = 1 f_i=1 fi=1,然后推式子:
f i = 1 d e g i + ∑ s o n 1 + f s o n + f i d e g i f_i={1 \over deg_i}+{\sum_{son} 1+f_{son}+f_i \over deg_i} fi=degi1+degison1+fson+fi
f i = d e g i + ∑ s o n f s o n f_i=deg_i+\sum_{son} f_{son} fi=degi+sonfson
然后计算不同起点每个 f i f_i fi的贡献 f i ⋅ s z i f_i \cdot sz_i fiszi
然后换根求不同终点的期望

P3232 [HNOI2013] 游走
这道跟上一道的区别是这道变成了图,上一道因为可以根据dfs序直接推过去,但这一道可能会形成多元环,所以用高斯消元
计算每个点经过次数的期望 f i f_i fi,那么对于一条边其经过的期望次数为 f u d e g u + f v d e g v {f_u \over deg_u} + {f_v \over deg_v} degufu+degvfv,然后贪心求贡献

f i f_i fi式子很简单,注意特判一下 i = = 1 i==1 i==1

P3830 [SHOI2012] 随机树
第一个询问: f i f_i fi表示叶子个数为 i i i个点的期望
f i = ( i − 1 ) ∗ f i − 1 + f i − 1 + 2 i f_i={(i-1)*f_{i-1}+f_{i-1}+2 \over i} fi=i(i1)fi1+fi1+2化简得 f i = f i − 1 + 2 i f_i=f_{i-1}+{2 \over i} fi=fi1+i2

第二个询问
E ( x ) = ∑ i = 1 ∞ P ( x > = i ) E(x)=\sum_{i=1}^{\infty} P(x>=i) E(x)=i=1P(x>=i)
那么设 f i , j f_{i,j} fi,j ,表示叶子个数为 i i i,深度大于等于 j j j的概率,那么根据公式就很好求期望了

发现不好转移,因为不知道每种情况的出现的概率,转移肯定是由左右子树叶子数转移,所以去想一个树,左右子树叶子数确定的情况下,出现的概率为多少:比如已知总叶子为 i i i,左子树叶子有 k k k个,那么右子树叶子有 i − k i-k ik个,那么这样构造的方案数:首先只考虑每次选左或右方案数 C i − 1 k = ( i − 1 ) ! k ! ( i − k − 1 ) ! C_{i-1}^{k}={(i-1)! \over k!(i-k-1)!} Ci1k=k!(ik1)!(i1)!,然后考虑左右子树的形态 k ! ( i − k − 1 ) ! k!(i-k-1)! k!(ik1)!,最终得 ( i − 1 ) ! (i-1)! (i1)!,发现和左右子树叶子数无关,那么 i i i个叶子的树种,左子树有 k k k个叶子的概率为 1 i − 1 1 \over i-1 i11

f i , j = f k , j − 1 + f i − k , j − 1 − f k , j − 1 ⋅ f i − k , j − 1 i − 1 f_{i,j}={f_{k,j-1}+f_{i-k,j-1}-f_{k,j-1} \cdot f_{i-k,j-1}\over i-1} fi,j=i1fk,j1+fik,j1fk,j1fik,j1

P4206 [NOI2005] 聪聪与可可
易证猫跟老鼠的距离一定是递减的,所以不会有后效性,状态设为 i , j i,j i,j表示猫和老鼠的位置
猫运动可以预处理,然后按照题意记忆化搜索即可

CF 908D New Year and Arbitrary Arrangement
倒推 f i , j f_{i,j} fi,j表示前缀中有 i i i a a a,已有的 a b ab ab对有 j j j组的期望对数
f i , j f_{i,j} fi,j f i + 1 , j , f i , i + j f_{i+1,j},f_{i,i+j} fi+1,j,fi,i+j转移 特判一下边界 i + j > = k i+j>=k i+j>=k时所需要的期望

推式子: p b ∑ a = 0 ∞ ( i + j + a ) p a a p_b\sum_{a=0}^{\infty} (i+j+a)p_a^a pba=0(i+j+a)paa
p b ( ( i + j ) ∑ a = 0 ∞ p a + ∑ a = 0 ∞ a p a a ) = p b ( i + j 1 − p a + p a ∑ a = 0 ∞ a p a a − 1 ) p_b((i+j)\sum_{a=0}^{\infty} p_a+ \sum_{a=0}^{\infty}ap_a^a)=p_b({i+j \over 1-p_a}+ p_a \sum_{a=0}^{\infty}ap_a^{a-1}) pb((i+j)a=0pa+a=0apaa)=pb(1pai+j+paa=0apaa1)

i + j + p b p a ( ∑ a = 0 ∞ p a a ) ′ = i + j + p b p a ( 1 1 − p a ) ′ i+j +p_b p_a (\sum_{a=0}^{\infty}p_a^a)'=i+j +p_b p_a ({1 \over 1-p_a})' i+j+pbpa(a=0paa)=i+j+pbpa(1pa1)
i + j + p b p a ( 1 − p a ) 2 = i + j + p b p a p b 2 = i + j + p a p b i+j +p_b {p_a \over (1-p_a)^2}=i+j+p_b{p_a \over p_b^2}=i+j+{p_a \over p_b} i+j+pb(1pa)2pa=i+j+pbpb2pa=i+j+pbpa

CF 398B Painting The Wall
找规律,状态为已经有几行几列合法,然后推出对所有点分类:
无贡献,贡献行,贡献列,贡献行列

CF 280C Game on Tree
考虑每一个点被选择的概率,如果它有一个祖先先选,该点不产生贡献;如果它在所有祖先前先选,产生 1 s u m 祖先 1 \over sum_{祖先} sum祖先1的贡献, s u m 祖先 = d e p sum_{祖先}=dep sum祖先=dep

CF 248E Piglet’s Birthday
发现 i i i货架没吃过的罐子<= a i a_i ai ,设 f i , j f_{i,j} fi,j表示第 i i i个货架,有 j j j个罐子没吃过的概率,然后可以模拟题目过程求出当前货架罐子数,每次贡献只会从移出罐子的货架产生,所以枚举每次转移中已经吃过的罐子数,组合数求概率

CF 123E Maze
固定起点为根节点 s s s,发现很难直接dp,考虑此题的一些特殊性质
假设现在要到 t t t,仅存在一条路径,假设当前点为 u u u,对其儿子随机排序,令当前正确走向的儿子为 v v v,则其它儿子有两种选择,一种排在 v v v前,另一种排在 v v v后,一旦排在 v v v前,会产生 2 ⋅ s z 2 \cdot sz 2sz的贡献,期望贡献为 s z sz sz ;排在 v v v后无贡献。

转换为,去掉 t t t子树后,树的大小为 s s s t t t的期望步数。
那么跟节点固定后,只需要维护去掉子树的期望大小 f f f,最后根节点 s s s的贡献为 ( n − f ) ∗ s p s (n-f)*sp_s (nf)sps

后续只需要通过换根dp,就可以求出各点作为根节点的贡献

CF103687 E
首先分类:
T 1 > = T 2 / S = = 0 T1>=T2/S==0 T1>=T2/S==0,说明只能用第二种回血方式,贪心的想,只要每次在还剩两滴血并且 ( 1 − p i ) (1-p_i) (1pi)的概率下花费 T 2 T2 T2时间即可

T 1 < T 2 T1<T2 T1<T2,首先还是能不回血就不回血,然后有法力优先用 T 1 T1 T1,没法力用 T 2 T2 T2。但是如果当前点是无限回蓝点,转移时可以直接从 f i + 1 , j , S f_{i+1,j,S} fi+1,j,S转移到 f i , j , S f_{i,j,S} fi,j,S(法力越高,期望肯定越低)。然后为了更好转移到 i − 1 i-1 i1,我们要更新一下不是满法力的状态(有个细节就是要注意转移的顺序,详见代码)

#include <bits/stdc++.h>
#define ll long long
double f[1005][15][10],p[1005];
bool vis[1005];
void yrzr(){
    int n,H,S;
    std::cin>>n>>H>>S;
    for (int i=0;i<n;i++){
        std::cin>>p[i];
        p[i]/=100;
    }
    int K;
    std::cin>>K;
    for (int i=1;i<=K;i++){
        int x;
        std::cin>>x;
        vis[x-1]=1;
    }
    double T1,T2;
    std::cin>>T1>>T2;
    for (int i=0;i<n;i++){
        for (int j=0;j<=H;j++){
            for (int k=0;k<=S;k++){
                f[i][j][k]=1e15;
            }
        }
    }
    if (T1>=T2||S==0){
        for (int i=n-1;i>=0;i--){
            f[i][2][0]=(1+p[i]*f[i+1][2][0]+(1-p[i])*T2)/p[i];
            for (int j=3;j<=H;j++){
                f[i][j][0]=1+p[i]*f[i+1][j][0]+(1-p[i])*f[i][j-1][0];
            }
        }
        std::cout<<std::fixed<<std::setprecision(7)<<f[0][H][0];
    }else{
        for (int i=n-1;i>=0;i--){
            if (!vis[i]){
                f[i][2][0]=(1+p[i]*f[i+1][2][0]+(1-p[i])*T2)/p[i];
                for (int k=1;k<=S;k++){
                    f[i][2][k]=1+p[i]*f[i+1][2][k]+(1-p[i])*(f[i][2][k-1]+T1);
                    for (int j=3;j<=H;j++){
                        f[i][j][k]=1+p[i]*f[i+1][j][k]+(1-p[i])*f[i][j-1][k];
                    }
                }
            }else{
                for (int j=2;j<=H;j++){
                    if (j==2){
                        f[i][2][S]=(1+p[i]*f[i+1][2][S]+(1-p[i])*T1)/p[i];
                    }else{
                        f[i][j][S]=1+p[i]*f[i+1][j][S]+(1-p[i])*f[i][j-1][S];
                    }
                    for (int k=j;k<=H;k++){
                        // f[i][j][S]=std::min(f[i][j][S],(k-j)*T1+f[i][k][S]);
                        //转移时不能直接这么转移,因为此时f[i][k][S]还没更新,所以我们可以直接手动化成f[i+1]的式子
                        f[i][j][S]=std::min(f[i][j][S],(k-j)*T1+(1+p[i]*f[i+1][k][S]+(1-p[i])*T1)/p[i]);
                    }
                    for (int k=0;k<=S;k++){
                        f[i][j][k]=std::min(f[i][j][k],f[i][j][S]);
                    }
                }

            }
        }
        std::cout<<std::fixed<<std::setprecision(7)<<f[0][H][S];
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值