概率充电器
链接:
题目大意:
在一棵树上,(n) 个节点有直接充电的概率 (q_i),边有帮节点间接充电的概率 (p_{u,v})。求每个点充上电的概率。
思路:
直接求充上电的概率不太行,求充不上的概率。设 (f_u) 表示 (u) 不亮的概率:
[f_u=(1-q_i)prod_{(u,v)in E}(1-p_{u,v}+p_{u,v}f_v)
]
树形 DP 再套换根即可:
[f_u=(1-q_i)prod_{vinmathrm{son}(u)}(1-p_{u,v}+p_{u,v}f_v)
]
设 (g_u) 表示 (u) 为根时的充不上电的答案:
[g_u=f_uleft(1-p_{mathrm{fa},u}+p_{mathrm{fa},u}left(frac{g_{mathrm{fa}}}{1-p_{mathrm{fa},u}+p_{mathrm{fa},u}f_u}
ight)
ight)
]
代码:
纪中机子爆栈,用 BFS 打。
const int N = 2e6 + 10;
inline ll Read()
{
ll x = 0, f = 1;
char c = getchar();
while (c != '-' && (c < '0' || c > '9')) c = getchar();
if (c == '-') f = -f, c = getchar();
while (c >= '0' && c <= '9') x = (x << 3) + (x << 1) + c - '0', c = getchar();
return x * f;
}
int n;
double p[N], f[N], g[N];
struct edge
{
int to, nxt; double val;
}e[N << 1];
int head[N], tot;
void add (int u, int v, double w)
{
e[++tot] = (edge){v, head[u], w}, head[u] = tot;
}
int q[N], H, T;
int fa[N], TO[N];
void bfs1 ()
{
memset (fa, -1, sizeof fa);
memset (q, 0, sizeof q);
H = 1, T = 0;
q[++T] = 1;
fa[1] = 0;
while(H <= T)
{
int u = q[H++];
f[u] = 1 - p[u];
for (int i = head[u]; i; i = e[i].nxt)
{
int v = e[i].to;
if (~fa[v]) continue;
fa[v] = u, TO[v] = i;
q[++T] = v;
}
}
for (int i = T; i; i--)
{
int u = fa[q[i]], v = q[i], j = TO[v];
f[u] *= 1 - e[j].val + e[j].val * f[v];
}
return;
}
void bfs2 ()
{
g[1] = f[1];
memset (q, 0, sizeof q);
memset (fa, -1, sizeof fa);
H = 1, T = 0;
q[++T] = 1;
fa[1] = 0;
while(H <= T)
{
int u = q[H++];
for (int i = head[u]; i; i = e[i].nxt)
{
int v = e[i].to;
if (~fa[v]) continue;
fa[v] = u;
g[v] = f[v] * (1 - e[i].val + e[i].val * (g[u] / (1 - e[i].val + e[i].val * f[v])));
q[++T] = v;
}
}
return;
}
double ans;
int main()
{
n = Read();
for (int i = 1; i < n; i++)
{
int u = Read(), v = Read(); double w;
scanf ("%lf", &w); w *= 0.01;
add(u, v, w), add(v, u, w);
}
for (int i = 1; i <= n; i++)
scanf ("%lf", &p[i]), p[i] *= 0.01;
bfs1 (), bfs2();
for (int i = 1; i <= n; i++)
ans += 1 - g[i];
printf ("%.6f", ans);
return 0;
}