loj2542. 「PKUWC2018」随机游走

本文探讨了一种结合容斥原理与树上动态规划的算法解决方案,针对特定的图论问题,通过高效的计算方法实现了对多组数据的快速响应。文章详细介绍了如何利用树上DP进行状态转移,并通过高斯消元优化计算过程,最终实现了一个复杂度为O(2^n*n + n*q)的算法。

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

题意

略。

题解

看到这个\(n \leq 18\),就知道可能是容斥。
结果是min-max容斥。
原来要求一个点出发到一个点集最晚访问的点的期望时间,现在求的则是一个点出发到一个点集最早访问的点的期望时间。
这个东西可以树上dp,而且显然dp出来的东西是可以高斯消元的。
但是这样复杂度不对。
考虑有个树上高消的方法,建立在线性关系的基础上。
可以推一发式子:
\[ f_i = \frac{1}{d_i} (f_{{fa}_i} + \sum_{j \in {son}_i} f_j) + 1 \]
并设
\[ f_i = a_i f_{{fa}_i} + b_i \]
(就不推了,懒)
\[ f_i = \frac{1}{d_i - \sum_{j \in {son}_i} a_j} f_{{fa}_i} + \frac{\sum_{j \in {son}_i} b_j + d_i}{d_i - \sum_{j \in {son}_i} a_j} \]
然后就可以从下到上回溯的时候\(\mathcal O(n \log p)\)计算了。
对于多组数据,其实可以直接预处理每种终点集合的答案,具体就是,终点集合里的每个点\(i\)\(a_i, b_i, f_i\)都先为0,然后以出发点为根dp,同时计算\(a\)\(b\)数组,则到这个子集的答案就是\(f_{root}\)(即\(b_{root}\)), 复杂度\(\mathcal O(2 ^ n n)\)
然后询问的时候使用min-max容斥,枚举子集,每次\(\mathcal O(1)\)计算即可。
总复杂度\(\mathcal O(2 ^ n n + nq)\)

#include <bits/stdc++.h>
using namespace std;

const int N = 19, mod = 998244353;
int n, q, rt, ans;
int a[N], b[N], d[N], fa[N];
int in[N], sgn[1 << N], mn[1 << N];
vector <int> g[N];
int power (int a, int b) {
    int ret = 1;
    for ( ; b; b >>= 1, a = 1ll * a * a % mod) {
        if (b & 1) {
            ret = 1ll * ret * a % mod;
        }
    }
    return ret;
}
int inv (int x) {
    return power(x, mod - 2);
}
void add (int x, int y) {
    g[x].push_back(y), ++d[x];
}
void dfs (int x, int p) {
    if (in[x]) {
        a[x] = b[x] = 0;
        return;
    }
    fa[x] = p, a[x] = b[x] = d[x];
    for (auto y : g[x]) {
        if (y != p) {
            dfs(y, x);
            a[x] = (a[x] - a[y] + mod) % mod;
            b[x] = (b[x] + b[y]) % mod;
        }
    }
    a[x] = inv(a[x]), b[x] = 1ll * b[x] * a[x] % mod;
}
int main () {
    scanf("%d%d%d", &n, &q, &rt);
    for (int i = 1, x, y; i < n; ++i) {
        scanf("%d%d", &x, &y);
        add(x, y), add(y, x);
    }
    for (int s = 1; s < (1 << n); ++s) {
        sgn[s] = -1;
        for (int i = 0; i < n; ++i) {
            if (s >> i & 1) {
                in[i + 1] = 1;
                sgn[s] = -sgn[s];
            } else {
                in[i + 1] = 0;
            }
        }
        dfs(rt, 0);
        mn[s] = b[rt];
    }
    for (int k, x, s; q; --q) {
        scanf("%d", &k), s = 0;
        for (int i = 1; i <= k; ++i) {
            scanf("%d", &x);
            s |= 1 << (x - 1);
        }
        ans = 0;
        for (int t = s; t; t = (t - 1) & s) {
            ans = (ans + mn[t] * sgn[t]) % mod;
        }
        ans = (ans % mod + mod) % mod;
        printf("%d\n", ans);
    }
    return 0;
}

转载于:https://www.cnblogs.com/psimonw/p/11442485.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值