「PKUWC2018」随机游走 题解

前言

好题,这个待定系数真的好用!

思路

说实话,看见这道题的第一反应真不是什么 \(\texttt{min-max}\) 容斥,而是状压。

但是既然都放在链接里面了,那我们就勉为其难的向这方面思考一下吧。

首先直接通过这个容斥转化题意,也就是说,我们只需要求得,对于任意一个点的集合 \(S\),使得从 \(x\) 出发,想要第一次到达 \(S\) 中的任意一点,所需要的期望次数。(简单来说就是通过该容斥将题意转化为 \(E(\min(S))\) 。)

具体来说,从 \(0\)\(2^n\) 枚举所有子集 \(S\),对于每个子集,考虑树形 \(\text{DP}\)

定义 \(dp_i\) 表示从 \(i\) 开始,到达 \(S\) 中的任意一点所需要的最少期望次数。

如果 \(i\in S\),显然 \(dp_i=0\)。否则,显然有 \(dp_i=\dfrac{dp_{fa_i}+\sum_{j\in son_i}dp_j}{deg_i} + 1\)。(其中,\(deg_i\) 表示 \(i\) 的度数)

但是显然,这样的转移是有后效性的。

一种比较暴力的想法是,把所有的式子列出来,然后高斯消元,复杂度为 \(n^3\times 2^n\),而且是在模意义下进行,实现代价巨大,不具有可行性。

我们考虑一个新式的 \(\text{trick}\): 待定系数法。

我们发现,这个式子是齐次的(且是一次的),而且必然可以通过高斯消元得到一个 \(dp_i\) 无后效性的表示。故我们考虑待定系数,假设 \(dp_i=k_i\times dp_{fa_i}+b_i\)

我们可以通过这个假设,将转移式子中的 \(dp_j\) 换掉,然后进行一波代数变形:

\[\begin{aligned} dp_i&=\dfrac{dp_{fa_i}+\left(\sum_{j\in son_i}k_j\right)\times dp_i+\sum b_j}{deg_i}+1 \\ (deg_i-\sum k_j)\times dp_i&=dp_{fa_i}+\sum b_j+deg_i\\ dp_i&=\dfrac{1}{(deg_i-\sum k_j)}\times dp_{fa_i}+\dfrac{\sum b_j+deg_i}{(deg_i-\sum k_j)} \end{aligned} \]

于是,我们有:\(k_i=\dfrac{1}{(deg_i-\sum k_j)},b_i=\dfrac{\sum b_j+deg_i}{(deg_i-\sum k_j)}\)

可以发现,\(k_i,b_i\) 的求法不具有后效性,且如果我们从 \(x\) 直接向下递归,则 \(dp_x=b_x\),因为没有父亲。

求出每个 \(S\) 之后,我们显然不能每输入一个就容斥一遍,也不能直接枚举子集,所以高维前缀和,根据容斥,\(S\) 大小为奇数时就是加,否则减。

代码

#include <bits/stdc++.h> 
using namespace std;
#define maxn 20
#define mod 998244353
int ksm(int x, int y)
{
	int sum = 1;
	while(y)
	{
		if(y & 1) sum = 1ll * sum * x % mod;
		y >>= 1, x = 1ll * x * x % mod;
	}	
	return sum;
}
int inv(int x)
{
	return ksm(x, mod - 2);
}
int n, q, x;
int fst[maxn], cnt;
struct node
{
	int tar, nxt;
}arr[maxn << 1];
void adds(int x, int y)
{
	arr[++cnt].tar = y, arr[cnt].nxt = fst[x], fst[x] = cnt;
}
bool belong[maxn];
int k[maxn], b[maxn];
int sum[1 << (maxn - 2)], ans[1 << maxn];
void dfs(int x, int last)
{
	if(belong[x]) return;
	int sumk = 0, sumb = 0, deg = 1;
	if(last == 0) deg--;
	for (int i = fst[x]; i; i = arr[i].nxt)
	{
		int j = arr[i].tar;
		if(j == last) continue;
		dfs(j, x);
		sumk += k[j], sumb += b[j], deg++;
		sumk %= mod, sumb %= mod;
	}
	k[x] = 1ll * inv(((deg - sumk) % mod + mod) % mod);
	b[x] = 1ll * (deg + sumb) % mod * inv(((deg - sumk) % mod + mod) % mod) % mod;
}
int Cnt[1 << maxn];
void init()
{
	for (int i = 1; i < (1 << n); ++i)
	{
		Cnt[i] = __builtin_popcount(i);
		memset(belong, 0, sizeof(belong));
		memset(k, 0, sizeof(k));
		memset(b, 0, sizeof(b));
		for (int j = 1; j <= n; ++j) if(i & (1 << j - 1)) belong[j] = true;
		dfs(x, 0);
		sum[i] = b[x] * (Cnt[i] & 1 ? 1 : -1);
		sum[i] = (sum[i] % mod + mod) % mod;
	}
	for (int j = 1; j <= n; ++j)
	{
		for (int i = 0; i < (1 << n); ++i)
		{
			if(!(i & (1 << j - 1)))
			sum[i + (1 << j - 1)] += sum[i], sum[i + (1 << j - 1)] %= mod;
		}
	}
}
int main()
{
	cin >> n >> q >> x;
	for (int i = 1; i < n; ++i)
	{
		int x, y;
		cin >> x >> y;
		adds(x, y);
		adds(y, x);
	}
	init();
	while(q--)
	{
		int m, now = 0;
		cin >> m;
		for (int i = 1; i <= m; ++i)
		{
			int x;
			cin >> x;
			now |= 1 << x - 1;
		}
		printf("%d\n", sum[now]);
	}
}
posted @ 2024-03-10 00:02  Saltyfish6  阅读(10)  评论(0编辑  收藏  举报
Document