概率充电器题解//2078/7/8

luogu食用,风味更佳,这条横线懒得删了:传送门

这题老师N年前就讲了,最近才做

让我们步入正题

这是一题概率期望dp+树形dp

话说为什么会想到概率期望dp+树形dp呢,首先我们来观察题目,发现它的每一个点之间都只有一条边,而且n-1条边也告诉我们,这是一颗完全二叉树。

而且每次充电的几率与导电的概率是给出的本文中用electricity数组表示给自己充电的概率 图的边权表示电线通电的概率,文章中暂且用b[a−>x]表示a指向b的边

那么第一个问题就是如何建树,正常人应该都会使用邻接表吧,在这里不做赘述。

既然是dp,那么我们应该考虑到dp绕不过去的一个问题:状态是什么。

我们从题意中可以知道,一个点有电的概率无非只有三种情况

  • 他自己给自己充电
  • 他的儿子给他充电
  • 他的爸爸给他充电

既然都说是树形dp了,那么按照树形dp的思想,我们应该把爸爸先扔在一边,只考虑儿子给他充电的情况,在本文中我们用FFF数组表示,那么不可避免的,还要设一个数组表示他爸爸给他充电的概率,本文中用G数组表示

所以状态设计:

  • F[x]他儿子或他自己给他充电的概率
  • G[x]他爸爸给他充电的概率

但是我们这么做就会遇到一个问题:概率该怎么相加呢?(我也不知道)很明显,这是一个难题,怎么办?既然这一种状态设计不够优秀,那么我们就换一种等价的设计方案:他没有电的概率,显然,有电的概率就是(1-他没有电的概率)

所以更换后的状态设计就是

  • F[x]他儿子或他自己给他充电的概率
  • G[x]他爸爸给他充电的概率

bingo,我们就这么获得了一个优秀的状态设计。 那么我们就来考虑状态的转移,先从比较简单的F数组开始

第一部分:F数组的状态转移

首先,大家想必都知道这两个公式

  1. 一件事件发生的概率与其不发生的概率之和 为1
  2. 两件事件同时发生的概率为(事件A发生的概率事件B发生的概率)

那么FFF数组的状态转移方程就显而易见了

F[x]=(1-electricity[x])*∏(F[son]+(1-F[son])(1-b[son−>x]))

解释一下:一个点的自己或自己的儿子不给他充电有一种先决条件

  1. 他自己不可以给自己充电,因为这是他自己的特性,所以并不受其他元素影响发生概率为(1-electricity[x]);

而他没有电的概率就有多种情况了

  1. 他儿子本来就没有电,发生概率为(F[son])
  2. 他儿子有电,不过那条电线不通电发生概率为((1-F[son])*(1-b[son>x]));

然后由于要一颗子树中的所有点都满足这一条件,所以要使用累乘符号∏

同样他的求出也是非常简单的只要dfsdfsdfs遍历一边图就可以了

第二部分:G数组的状态转移

这个就比较麻烦了,这个我们先来看看一个点最终没有电概率,本文中用ttt表示

t=G[father]*F[father]/(F[x]+(1-F[x])(1-b[father>x]))

解释一下:一个点xxx没有电的概率是要满足一下两个条件

  1. 他自己或他儿子没有给他充电发生概率为(F[x])
  2. 他爸爸没有给他充电,发生概率为(G[x])

然而由于我们计算GGG数组的时候也乘上了一个  F[x]+(1-F[x])(1-b[father−>x]),所以为了防止出现类似死循环的情况,需要把他再次除去。

那么通过乘法原理,就可以得出一个点xxx没有电的最终概率为

G[x]=t+(1-t)*(1-b[father>x])

这个转移类比FFF数组的状态转移

然后通过类似高斯消元的方法就可以求出GGG[x]了(不会高斯消元的同学不必担心,因为这在代码中并没有用到,只要在dfsdfsdfs的是后顺便求出就可以了)

第三部分:总概率的计算

这一部分想必我不说大家都知道了 根据乘法原理,一个点最终没有电的概率为

  • 他爸爸不给他点的概率*他自己或他儿子不给他电的概率

所以一个点xxx有电的概率就自然是

1-F[x]*G[x]

第四部分:代码注意

注意本题的数据类型要多开double的数据,而int类型的数据就算除去了一个100.0,还是int类型,会自动舍去小数部分(作者就是因为这个错误卡了好久)

第五部分:哔哔完毕,排上代码(好像有点长)

(2984ms(不开O2),41.72MB,时间复杂度O(n))

#include 
using namespace std;
struct cutetree {
    int to,nxt;
    double value;
}e[1000020]={};
int linkk[500010]={};
int len;
double electricity[500010]={};
int x,y;
int elec;
double G[500010]={};
double F[500010]={};
bool flag[500010]={};
int n;

void insert(int x,int y,double value) {
    e[++len].value=value;
    e[len].to=y;
    e[len].nxt=linkk[x];
    linkk[x]=len;
}

void dfs(int x) {
    flag[x]=true;
    F[x]=1.0-electricity[x];
    for(int i=linkk[x];i;i=e[i].nxt)
    {
        if(flag[e[i].to])  continue;
        dfs(e[i].to);
        F[x]*=F[e[i].to]+(1.0-F[e[i].to])*(1.0-e[i].value);
    }
    flag[x]=false;
}

void findf(int u) {
    flag[u]=true;
    for(int i=linkk[u];i;i=e[i].nxt)
    {
        if(flag[e[i].to])  continue;
        double t=G[u]*F[u]/(F[e[i].to]+(1.0-F[e[i].to])*(1.0-e[i].value));
        G[e[i].to]=t+(1.0-t)*(1.0-e[i].value);
        findf(e[i].to);
    }
}

int main() {
    scanf("%d",&n);
    for(int i=1;iscanf("%d%d%d",&x,&y,&elec);
        insert(x,y,elec/100.0);
        insert(y,x,elec/100.0);
    }
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&elec);
        electricity[i]=elec/100.0;
    }
    G[1]=1.0;
    dfs(1);
    findf(1);
    double ans;
    for(int i=1;i<=n;i++)
        ans+=(1.0-G[i]*F[i]);
    printf("%.6lf\n",ans);
    return 0;
}
最后吐槽一句,这充电器(永动机诶)绝对能拿诺贝尔奖

你可能感兴趣的:(题解)