「PKUWC2018」随机游走(加强版)

Description

给定一张 n n n 个点, m m m 条边的无向图。

q q q 次询问,每次询问给定一个集合 S S S 和一个点 x x x。求如果从 x x x 出发一直随机游走(每次等概率走向相邻的点),直到点集 S S S 中所有点都至少经过一次的话,期望经过多少条边。

n ≤ 18 , m ≤ n ( n − 1 ) 2 , q ≤ 100000 n≤18,m≤\frac{n(n-1)}{2},q≤100000 n18m2n(n1)q100000, 答案对 998244353 998244353 998244353 取模。

Solution

f [ S ] [ i ] f[S][i] f[S][i] 表示已经经过 S S S 集合中的所有点,现在位于点 i i i,将剩下的点全部经过,期望还要走多少条边(保证 i ∈ S i∈S iS)。

对于询问 S , x S,x S,x,答案就是 f [ ( a l l − S ) ∣ x ] [ x ] f[(all-S)|x][x] f[(allS)x][x]

初值 f [ a l l ] [ i ] = 0 f[all][i]=0 f[all][i]=0

递推式( d u d_u du 表示点 u u u 的度数):
f [ S ] [ u ] = 1 d u ∑ u , v 有 边 ( f [ S ∣ v ] [ v ] + 1 ) f[S][u]=\frac{1}{d_u}\sum_{u,v有边}(f[S|v][v]+1) f[S][u]=du1u,v(f[Sv][v]+1)

直接高斯消元是 O ( ( 2 n n ) 3 ) O((2^nn)^3) O((2nn)3) 的,过不去。

发现要么 S ∈ S ∣ v S∈S|v SSv,要么 S = S ∣ v S=S|v S=Sv

我们可以把方程改写成:
f [ S ] [ u ] − 1 d u ∑ u , v 有 边 , S ∣ v = S ( f [ S ] [ v ] + 1 ) = 1 d u ∑ u , v 有 边 , S ∈ S ∣ v ( f [ S ∣ v ] [ v ] + 1 ) f[S][u]-\frac{1}{d_u}\sum_{u,v有边,S|v=S}(f[S][v]+1)=\frac{1}{d_u}\sum_{u,v有边,S∈S|v}(f[S|v][v]+1) f[S][u]du1u,v,Sv=S(f[S][v]+1)=du1u,v,SSv(f[Sv][v]+1)

考虑按 S S S 从大到小递推。

也就是说我们求 f [ S ] [ u ] f[S][u] f[S][u] 的时候已经知道所有的 f [ T ] [ v ] f[T][v] f[T][v] 了。( u , v u,v u,v 可以是任何一个点, S S S T T T 的真子集。)

也就是说式子中 1 d u ∑ u , v 有 边 , S ∈ S ∣ v ( f [ S ∣ v ] [ v ] + 1 ) \frac{1}{d_u}\sum_{u,v有边,S∈S|v}(f[S|v][v]+1) du1u,v,SSv(f[Sv][v]+1) 的值已知。

那么我们只要把 f [ S ] [ 1 ] f[S][1] f[S][1] ~ f [ S ] [ n ] f[S][n] f[S][n] n n n 个变量拿出来跑高斯消元即可。

总共跑 2 n 2^n 2n 次高斯消元,时间复杂度 O ( 2 n n 3 ) O(2^nn^3) O(2nn3)

Code

#include <bits/stdc++.h>

using namespace std;

const int mod = 998244353, e = 805, o = (1 << 18) + 5; 
int adj[e], nxt[e], num, go[e], f[o][20], n, m, day, a[25][25], b[25], pos[25];
int c[25], tn, id[25], d[25], h[25], cnt;

inline void add(int x, int y)
{
	nxt[++num] = adj[x];
	adj[x] = num;
	go[num] = y;
	nxt[++num] = adj[y];
	adj[y] = num;
	go[num] = x;
}

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

inline int ksm(int x, int y)
{
	int res = 1;
	while (y)
	{
		if (y & 1) res = 1ll * res * x % mod;
		y >>= 1;
		x = 1ll * x * x % mod;
	}
	return res;
}

inline void put(int n)
{
	int i, j;
	for (i = 1; i <= n; i++, putchar('\n'))
	{
		for (j = 1; j <= n; j++) printf("%d ", a[i][j]);
		printf(" %d", b[i]);
	}
}

inline void gauss(int n)
{
	int i, j, k;
	for (i = 1; i <= n; i++)
	for (j = 1; j <= n; j++)
	a[i][j] = (a[i][j] % mod + mod) % mod; 
	for (i = 1; i <= n; i++) b[i] = (b[i] % mod + mod) % mod;
	for (i = 1; i < n; i++)
	{
		k = i;
		for (j = i; j <= n; j++)
		if (a[j][i])
		{
			k = i;
			break;
		}
		if (k != i)
		for (j = i; j <= n; j++) swap(a[k][j], a[i][j]);
		swap(b[k], b[i]);
		for (j = i + 1; j <= n; j++)
		if (a[j][i])
		{
			int x = 1ll * a[j][i] * ksm(a[i][i], mod - 2) % mod;
			for (k = i; k <= n; k++)
			a[j][k] = (a[j][k] - 1ll * a[i][k] * x % mod + mod) % mod;
			b[j] = (b[j] - 1ll * b[i] * x % mod + mod) % mod;
		}
	}
	for (i = n; i >= 1; i--)
	{
		c[i] = b[i];
		for (j = i + 1; j <= n; j++) 
		c[i] = (c[i] - 1ll * a[i][j] * c[j] % mod + mod) % mod;
		c[i] = 1ll * c[i] * ksm(a[i][i], mod - 2) % mod;
	}
}

int main()
{
	int i, j, x, y, s, u;
	n = read(); m = read();
	while (m--)
	{
		x = read();
		y = read();
		d[x]++;
		d[y]++;
		add(x, y);
	}
	tn = 1 << n;
	for (s = tn - 2; s >= 0; s--)
	{
		cnt = 0;
		for (i = 1; i <= n; i++)
		if (s & (1 << i - 1)) 
		{
			id[++cnt] = i;
			pos[i] = cnt;
		}
		memset(a, 0, sizeof(a));
		for (i = 1; i <= cnt; i++)
		{
			int u = id[i];
			a[i][i] = d[u];
			b[i] = d[u];
			for (j = adj[u]; j; j = nxt[j])
			{
				int v = go[j];
				if (s & (1 << v - 1)) a[i][pos[v]] = mod - 1;
				else b[i] = (b[i] + f[s | (1 << v - 1)][v]) % mod;
			}
		}
		gauss(cnt);
		for (i = 1; i <= cnt; i++) f[s][id[i]] = c[i];
	}
	day = read();
	while (day--)
	{
		n = read();
		y = tn - 1;
		for (i = 1; i <= n; i++)
		{
			x = read();
			y ^= 1 << x - 1;
		}
		u = read();
		printf("%d\n", f[y | (1 << u - 1)][u]);
	}
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值