BZOJ 3566: [SHOI2014]概率充电器 期望DP + 树形DP

2 篇文章 0 订阅
1 篇文章 0 订阅

Time Limit: 40 Sec Memory Limit: 256 MB
Submit: 1276 Solved: 558

Description

著名的电子产品品牌 SHOI 刚刚发布了引领世界潮流的下一代电子产品——概率充电器:
“采用全新纳米级加工技术,实现元件与导线能否通电完全由真随机数决定!SHOI 概率充电器,您生活不可或缺的必需品!能充上电吗?现在就试试看吧!

SHOI 概率充电器由 n-1 条导线连通了 n 个充电元件。进行充电时,每条导线是否可以导电以概率决定,每一个充电元件自身是否直接进行充电也由概率决定。
随后电能可以从直接充电的元件经过通电的导线使得其他充电元件进行间接充电。
作为 SHOI 公司的忠实客户,你无法抑制自己购买 SHOI 产品的冲动。在排了一个星期的长队之后终于入手了最新型号的 SHOI 概率充电器。
你迫不及待地将 SHOI 概率充电器插入电源——这时你突然想知道,进入充电状态的元件个数的期望是多少呢?

Input

第一行一个整数:n。概率充电器的充电元件个数。充电元件由 1-n 编号。
之后的 n-1 行每行三个整数 a, b, p,描述了一根导线连接了编号为 a 和 b 的
充电元件,通电概率为 p%。
第 n+2 行 n 个整数:qi。表示 i 号元件直接充电的概率为 qi%。

Output

输出一行一个实数,为进入充电状态的元件个数的期望,四舍五入到六位小数

Sample Input

3

1 2 50

1 3 50

50 0 0

Sample Output

1.000000

HINT

对于 100%的数据,n≤500000,0≤p,qi≤100。

Source

By 佚名提供


这题好难啊…真的搞了好长时间才搞懂,网上题解貌似也都不是特别地清真,除了有两份还比较清晰的,以下是一份,如果我的看不懂可以去看这篇dalao讲解


首先我们定义几个东西

f[i]

表示由它的整个子树给他供不上电的概率(这个里面包括了自己不来电的情况,因为自己也算在自己所在的子树中,也包含了所有的孩子不给他供电的情况,利用乘法原理计算)

g[i]

表示由他的父亲不能给他供上电的概率

显然第 i 个点不被供电的概率是f[i]g[i],也就是父亲不给他电,孩子不给他电,自己不来电

显然我们可以发现光是定义一个 f[i] g[i] 不是很容易转移,两者之间貌似也没有太大的联系可以直接去转移,于是我们再定义一个数组 h[i]

h[i]

表示i点对其父亲充不上电的贡献,换句话说,表示i点不能给他父亲供电的概率

那么显然有

h[i]=f[i]+(1.0f[i])(1.0p)

其中 p 表示i点到 i 点的父亲的边被通电的概率

则显然h[i]表示他不能给他父亲供电的概率等于他本来就没有电的概率加上他本来有电,但是到父亲的这根线不通电,于是乘以这根线不通电的概率

而对于叶节点,显然叶节点的 f 值就等于自己不来电的概率,h值仍然满足这个式子

f[i] 的求解,显然应该有

f[i]=(1.0p[i])Πhv

这里的 p[i] 表示 i 自来电的概率

那么最后一个问题是求解g[i],其实 g[i] 的求解也不是很难,但是由于我菜,真的是理解了好长时间

我们对于 g[v] 的求解,令 u 为他的父亲,则定义一个变量为t

t=h[v]<eps?0:g[u]f[u]h[v]

最后
g[v]=t+(1.0t)(1.0p)

这里的 p 表示和父亲连的边的通电几率
我们先看前面这个,如果h[v] 0 的话,说明v一定可以向 u 充电,什么意思呢?就是说v一定有点,并且 v u的线一定是百分之百流通的, 这种情况的话,我们最后对于这个 v 点,计算1g[v]f[v]的时候就会发现,虽然之后计算了一个 g[v] ,但是其实是没有作用的,不影响答案的,因为 f[v] 必然等于 0 ,因此不会影响答案,对于第二种情况,如果h[v]有值,则计算 t g[u]f[u]h[v],其中的道理其实很简单,也就是现在父亲 u 不能由其父亲通电,不能自来电,不能由除了v以外的孩子来电,则只能通过 v 来电,这这种情况就是不能给v供电的概率,因为其他路全部封死了,没有电力来源,因此不能给 v 输送电力,直接不能送电力的几率加上可以送电力的几率乘以线不导电的概率就是我们的答案了
比如下面这幅图就表现了这种无法u无法向 v 供电的情况,此时圈起来的部分都不向u供电,包括 u <script type="math/tex" id="MathJax-Element-7982">u</script>自己也不来电
这里写图片描述


#include <cstdio>
#include <cstring>
#include <iostream>

using namespace std;

const int MAXN = 500000 + 10;
int head[MAXN], tail, fa[MAXN];
double f[MAXN], h[MAXN], g[MAXN];
struct Line{ int to, nxt; double p; }line[ MAXN * 2 ];

void add_line( int from, int to, double p ) {
    line[++tail].nxt = head[from];
    head[from] = tail;
    line[tail].to = to;
    line[tail].p = p; 
}
void dfs1( int u, int fat ) {
    fa[u] = fat;
    for( register int i = head[u]; i; i = line[i].nxt ) {
        int v = line[i].to;
        if( v == fat ) continue;
        dfs1( v, u );
        h[v] = f[v] + ( 1 - f[v] ) * ( 1.0 - line[i].p );
        f[u] *= h[v];
    }
}
void dfs2( int u, int fat ) {
    for( register int i = head[u]; i; i = line[i].nxt ) {
        int v = line[i].to;
        if( v == fat ) continue;
        double t = h[v] < 1e-6 ? 0 : g[u] * f[u] / h[v];
        g[v] = t + ( 1.0 - t ) * ( 1.0 - line[i].p );
        dfs2( v, u );
    }
}
int main( ) { int n;
    scanf( "%d", &n ); int ff, tt, pp;
    for( register int i = 1; i <= n - 1; i++ ) {
        scanf( "%d%d%d", &ff, &tt, &pp );
        add_line( ff, tt, pp / 100.0 );
        add_line( tt, ff, pp / 100.0 );
    }
    for( register int i = 1; i <= n; i++ ) { int tmp;
        scanf( "%d", &tmp );
        f[i] = 1.0 - tmp / 100.0;
    }
    dfs1( 1, 0 );
    g[1] = 1.0; 
    dfs2( 1, 0 );

    double ans = 0.0;
    for( register int i = 1; i <= n; i++ ) ans += 1.0 - f[i] * g[i];
    printf( "%.6lf\n", ans );
    return 0;
}

这里写图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
题目描述 有一个 $n$ 个点的棋盘,每个点上有一个数字 $a_i$,你需要从 $(1,1)$ 走到 $(n,n)$,每次只能往右或往下走,每个格子只能经过一次,路径上的数字和为 $S$。定义一个点 $(x,y)$ 的权值为 $a_x+a_y$,求所有满足条件的路径中,所有点的权值和的最小值。 输入格式 第一行一个整数 $n$。 接下来 $n$ 行,每行 $n$ 个整数,表示棋盘上每个点的数字。 输出格式 输出一个整数,表示所有满足条件的路径中,所有点的权值和的最小值。 数据范围 $1\leq n\leq 300$ 输入样例 3 1 2 3 4 5 6 7 8 9 输出样例 25 算法1 (树形dp) $O(n^3)$ 我们可以先将所有点的权值求出来,然后将其看作是一个有权值的图,问题就转化为了在这个图中求从 $(1,1)$ 到 $(n,n)$ 的所有路径中,所有点的权值和的最小值。 我们可以使用树形dp来解决这个问题,具体来说,我们可以将这个图看作是一棵树,每个点的父节点是它的前驱或者后继,然后我们从根节点开始,依次向下遍历,对于每个节点,我们可以考虑它的两个儿子,如果它的两个儿子都被遍历过了,那么我们就可以计算出从它的左儿子到它的右儿子的路径中,所有点的权值和的最小值,然后再将这个值加上当前节点的权值,就可以得到从根节点到当前节点的路径中,所有点的权值和的最小值。 时间复杂度 树形dp的时间复杂度是 $O(n^3)$。 C++ 代码 算法2 (动态规划) $O(n^3)$ 我们可以使用动态规划来解决这个问题,具体来说,我们可以定义 $f(i,j,s)$ 表示从 $(1,1)$ 到 $(i,j)$ 的所有路径中,所有点的权值和为 $s$ 的最小值,那么我们就可以得到如下的状态转移方程: $$ f(i,j,s)=\min\{f(i-1,j,s-a_{i,j}),f(i,j-1,s-a_{i,j})\} $$ 其中 $a_{i,j}$ 表示点 $(i,j)$ 的权值。 时间复杂度 动态规划的时间复杂度是 $O(n^3)$。 C++ 代码

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值