[PKUWC2018]随机游走(Min-max容斥+FWT/FMT)

题目:洛谷P5643LOJ#2542

题目描述:

一棵\(n\)个点的树,从\(x\)出发,每次等概率选择一条与之相连的边,朝那条边连着的另外一个节点走过去

\(Q\)次询问,每次询问这\(n\)个点的一个子集\(S\),求要把\(S\)中的所有点经过至少一次的最小期望步数

\(x\)一开始就经过了一次

\(n \leq 18\)\(Q \leq 5000\)

蒟蒻题解:

对于确定了一个集合\(S\),令\(max(S)\)表示经过\(S\)所有的点至少一次,\(min(T)\)表示经过\(T\)中任意一个点一次,根据\(Min-max\)容斥,则有:

\[E(max(S)) = \sum_{T \subseteq S} (-1)^{|T|-1} E(min(T)) \]

其中\(E(max(S))\)为经过\(S\)所有点至少一次的最小期望步数,\(E(min(T))\)为经过\(T\)中任意一个点一次的最小期望步数

考虑计算\(E(min(T))\),令\(f(i)\)为从\(i\)出发知道到达\(T\)中任意一个点为止的期望步数

  • \(i \in T\),则\(f(i) = 0\)
  • \(i \notin T\),则\(f(i) = 1 + \frac{1}{deg(i)} \sum_{(i,j) \in E}f(j)\)

现在我们想要知道的是\(f(x)\)的答案,不妨让\(x\)作为这棵树的根

由于最终是要算出根节点的答案,尝试将\(fa_i\)单独提出来

\[f(i) = 1 + \frac{1}{deg(i)} \sum_{i = fa_j} f(j) + \frac{1}{deg(i)} f(fa_i) \]

由于叶子节点和\(S\)集合中的点是不需要考虑孩子的情况的,我们可以尝试将每个\(f(i)\)表示成\(A_i + B_if(fa_i)\)

这样我们就可以得到:

\[f(i) = 1 + \frac{1}{deg(i)} \sum_{i = fa_j}(A_j + B_jf(i)) + \frac{1}{deg(i)} f(fa_i) \]

\[f(i) = \frac{deg(i) + \sum_{i = fa_j}A_j}{deg(i) - \sum_{i = fa_j}B_j} + \frac{f(fa_i)}{deg(i) - \sum_{i = fa_j}B_j} \]

其中\(A(i) = \frac{deg(i) + \sum_{i = fa_j}A_j}{deg(i) - \sum_{i = fa_j}B_j}\)\(B(i) = \frac{1}{deg(i) - \sum_{i = fa_j}B_j}\)

可以一直往上转移,由于\(x\)为根,它没有父亲,所以\(f(x) = A(x)\)

这样我们就可以\(\mathcal O(n)\)算出经过集合\(T\)任意一个点为止的期望步数\(E(min(T))\)

预处理出所以的\(E(min(T))\),就可以在\(\mathcal O(q2^n)\)时间内算出答案

观察\(Min-max\)容斥的形式:

\[E(max(S)) = \sum_{T \subseteq S} (-1)^{|T|-1} E(min(T)) \]

我们可以令\(g(T)\)表示\((-1)^{|T|-1} E(min(T))\),则有:

\[E(max(S)) = \sum_{T \subseteq S} g(T) \]

对于求所有的子集之和,可以用\(FWT/FMT\)优化到\(\mathcal O(n2^n)\),这边用的是\(FWT\)或变换

参考程序:

#include<bits/stdc++.h>
using namespace std;
#define Re register int
typedef long long ll;

const int N = 270000, p = 998244353;
int n, q, x, m, cnt, hea[20], nxt[40], to[40], A[20], B[20], ct[N], f[N];

inline int read()
{
	char c = getchar();
	int ans = 0;
	while (c < 48 || c > 57) c = getchar();
	while (c >= 48 && c <= 57) ans = (ans << 3) + (ans << 1) + (c ^ 48), c = getchar();
	return ans;
}

inline void write(int x)
{
	int num = 0;
	char sc[15];
	if (!x) sc[num = 1] = 48;
	while (x) sc[++num] = x % 10 + 48, x /= 10;
	while (num) putchar(sc[num--]);
	putchar('\n');
}

inline int fpow(ll x, int y)
{
	ll z = 1;
	while (y)
	{
		if (y & 1) z = z * x % p;
		x = x * x % p, y >>= 1;
	}
	return z;
}

inline int inc(int x, int y)
{
	x += y;
	return x < p ? x : x - p;
}

inline void add(int x, int y)
{
	nxt[++cnt] = hea[x], to[cnt] = y, hea[x] = cnt;
}

inline void dfs(int x, int fa, int S)
{
	if ((S >> (x - 1)) & 1)
	{
		A[x] = B[x] = 0;
		return;
	}
	int dg = fa ? 1 : 0, sA = 0, sB = 0;
	for (Re i = hea[x]; i; i = nxt[i])
	{
		int u = to[i];
		if (u == fa) continue;
		dfs(u, x, S);
		++dg, sA = inc(sA, A[u]), sB = inc(sB, B[u]);
	}
	B[x] = fpow(dg - sB + p, p - 2), A[x] = 1ll * (dg + sA) * B[x] % p;
}

int main()
{
	n = read(), q = read(), x = read(), m = 1 << n;
	for (Re i = 1; i < n; ++i)
	{
		int u = read(), v = read();
		add(u, v), add(v, u);
	}
	for (Re i = 0; i < m; ++i) dfs(x, 0, i), ct[i] = ct[i >> 1] + (i & 1), f[i] = 1ll * ((ct[i] & 1) ? 1 : p - 1) * A[x] % p;
	for (Re i = 1; i <= n; ++i)
	{
		int u = 1 << i, v = 1 << (i - 1);
		for (Re j = 0; j < m; j += u)
			for (Re k = 0; k < v; ++k) f[j | k | v] = inc(f[j | k | v], f[j | k]);
	}
	while (q--)
	{
		int u = read(), S = 0;
		while (u--) S |= 1 << (read() - 1);
		write(f[S]);
	}
	return 0;
}
posted @ 2021-06-04 16:23  clfzs  阅读(65)  评论(0)    收藏  举报