蚊子 (树形期望dp)

这篇博客探讨了一种树形动态规划方法来解决蚊子在经过被灭蚊器影响的点时的生存概率问题。文章介绍了从简单的BFS遍历到复杂的树分治算法,包括不同算法的思路、复杂度分析以及对应得分,最终提供了一个满分解决方案,该方案利用树形DP计算以特定点为LCA的所有路径的贡献之和。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

蚊子(mosquito)

10.26

首先每只蚊子的贡献是独立的.如果一只蚊子经过了k个会被灭蚊器影响的点那么这个蚊子对答案的贡献是1-(1-p/q)k.
用1遍bfs求出哪些点是被灭蚊器影响的点.然后进行不同的处理.
n为树的点数,m为叶子节点数.
算法1:
对于每一只蚊子,做一遍bfs,O(m2)次bfs,复杂度O(nm2),期望得分10分
算法2:
通过一次bfs我们可以统计出一个叶子节点出发的所有蚂蚁的贡献.那么只需要m次bfs就可以了.复杂度O(nm).期望得分30分.
算法3:
对于d=0的测试点3,只有1号点被控制.那么经过了1号点的蚊子会贡献P/Q,我们只需要数一数有多少蚊子经过了1号点.这是非常好数的.期望得分10分.
算法4:
对于p/q=1的测试点4,蚊子只要经过被控制的点就必死无疑.我们只需要统计有多少只蚊子至少经过了一个被控制的点.我们以1为根建树,f[i]表示i所在子树内叶节点的个数,对于每一个被控制的点,我们统计以其为lca的路径条数.复杂度O(n). 这部分分对标算是有提示的.
算法5:
考虑对每一条路径进行讨论,求解lca之后算出这条路径经过的被影响的点数.复杂度应当为O(m2logn),但是细节较多,出题人没有写.
可以通过第6,7个测试点,结合前面的算法可以得到70分.
算法6(满分做法):
考虑对算法4进行拓展.此时每只蚊子的贡献是1-(1-p/q)k.我们可以先不管前面那个1,把后面的(1-p/q)k的和求出来,最后用m*(m-1)减去那个和就可以了.
一条路径可以在LCA处拆成两条.我们考虑如何求出以某个点为LCA的所有路径的贡献之和.一条路径可以拆成两部分,一部分是从一个叶节点走到LCA,另一部分是从LCA走到另一个叶节点.把这两部分看作两条”半路径”.
记g[i]为以i为LCA的所有半路径的贡献之和.(即:对于i子树内的每个叶节点x,g[i]+=(1-p/q)k,k是x到i的半路径上的被控制点的个数).g[i]可以通过O(n)树形DP得到.
接下来我们通过g[i]求出以每个点为LCA的路径的贡献之和.
对于点x,考虑它的所有儿子,每一对儿子(u,v)的贡献是g[u]g[v](1-p/q)
注意,(u,v)和(v,u)都需要算一次.
直接暴力枚举每一对儿子会超时.我们通过乘法分配律可以O(儿子个数)的时间复杂度算出来.总的时间复杂度仍为O(n).空间复杂度也为O(n).但是常数可能较大,因此出题人给了5s的时限.
算法6:
本题属于树上路径统计问题,可以树分治.复杂度O(nlogn).期望得分50分.
可能有选手通过大力卡常数能够直接用树分治AC此题.

#include <cstdio>
#include <algorithm>
#include <cstring>
#include <iostream>
#include <queue>
#define N 5000005
#define LL long long
#define mod 1000000007
using namespace std;

inline int read() {
    int x = 0, f = 1; char ch = getchar();
    while(ch < '0' | ch > '9') {if(ch == '-')f = -1; ch = getchar();}
    while(ch >= '0' && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();}
    return x * f;
}

int n, idc, a, b, d, p, q;
int num[N], dep[N], head[N];
LL ans, s, f[N];

LL mpow(LL a, LL b) {
    LL rt = 1;
    for(; b; b >>= 1, a = a * a % mod)
    if(b & 1) rt = rt * a % mod;
    return rt;
}

struct Edge{
    int to, nxt;
}ed[N << 1];

void adde(int u, int v) {
    ed[++idc].to = v;
    ed[idc].nxt = head[u];
    head[u] = idc;
}

void dfs1(int u, int fa) {
    bool flag = 0;
    for(int i = head[u]; i; i = ed[i].nxt) {
        int v = ed[i].to;
        if(v == fa) continue;
        flag = 1;
        dep[v] = dep[u] + 1;
        dfs1(v, u);
        num[u] += num[v];
        if(dep[u] <= d) f[u] = (f[u] + f[v] * s % mod) % mod;//
    }
    if( !flag ) num[u] = 1;
    if(dep[u] > d) f[u] = num[u];
    if(!flag && dep[u] <= d) f[u] = s;
}

void dfs2(int u, int fa) {
    for(int i = head[u]; i; i = ed[i].nxt) {
        int v = ed[i].to;
        if(v == fa) continue;
        dfs2(v, u);
        LL t1 = f[v] % mod, t2 = f[u];
        if( dep[u] <= d ) t2 -= (f[v] * s % mod);//子树内和子树外到达此点的期望个数(两边相乘) 
        else t2 -= f[v];
        t2 = (t2 + mod) % mod;
        ans = ( ans + t1 * t2 % mod ) % mod;
    }
}

int main() {
    freopen ("mosquito.in", "r", stdin);
    freopen ("mosquito.out", "w", stdout);
    n = read();
    for(int i=1; i<n; i++) {
        a = read(), b = read();
        adde(a, b); adde(b, a);
    }
    d = read(); p = read(); q = read();
    s = 1LL * (q - p) * mpow(q, mod-2) % mod; // s 幸存概率 
    dfs1(1, 1);
    dfs2(1, 1);
    LL cc = 1LL * num[1] * (num[1] - 1) % mod;
    ans = ((cc - ans) % mod + mod) % mod;
    cout << ans << endl;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值