Processing math: 4%

【JZOJ6385】【NOIP2019模拟2019.10.23】B

题目大意

给出一棵树,其中1为根。之后每个点向父亲的父亲再连一条边,求得到的图中,每个点走到1的期望步数(等概率向相邻点走去)。
保证i的父亲fa_i<i
n\leq 2000

Solution

首先列方程,设f_i表示i走向1的期望步数,有:

f_x=1+\frac{1}{d_x}\sum f_y

其中,d_xx的度数,y是与x相邻的所有点。

直接高斯消元解方程,复杂度O(n^3),过不了。

观察这个系数矩阵的特点,第x行的系数只会在父亲,父亲的父亲,儿子,儿子的儿子处有值。如果我们从儿子往根消元,每次用第x行消去fa_x行和fa_{fa_x}行的第x列,那么最后会得到一个下三角矩阵。这时第x行的系数只会在父亲,父亲的父亲处有值,我们从根往儿子消元,就能得到对角线矩阵了。

复杂度O(n^2)。这个做法利用了系数矩阵的特点,减少消元次数,真是妙不可言~~~

Code

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

const int N = 2007, P = 998244353;

int n, fa[N], d[N], c[N][N], b[N];

int tot, st[N], nx[N << 2], to[N << 2];
void add(int u, int v) {
	to[++tot] = v, nx[tot] = st[u], st[u] = tot;
	to[++tot] = u, nx[tot] = st[v], st[v] = tot;
	++d[u], ++d[v];
}

int pow(int a, int b) {
	int ret = 1;
	for (; b; a = 1ll * a * a % P, b >>= 1) if (b & 1) ret = 1ll * ret * a % P;
	return ret;
}

int main() {
	//freopen("in", "r", stdin);
	freopen("b.in", "r", stdin);
	freopen("b.out", "w", stdout);
	scanf("%d", &n);
	for (int i = 2; i <= n; ++i) scanf("%d", &fa[i]), add(i, fa[i]);
	for (int i = 1; i <= n; ++i) if (fa[fa[i]]) add(i, fa[fa[i]]);
	c[1][1] = 1;
	for (int i = 2; i <= n; ++i) {
		c[i][i] = 1;
		for (int j = st[i]; j; j = nx[j]) c[i][to[j]] = P - pow(d[i], P - 2);
		b[i] = 1;
	}
	for (int i = n; i >= 1; --i) {
		if (fa[i]) {
			int j = fa[i], res = 1ll * c[j][i] * pow(c[i][i], P - 2) % P;
			for (int k = 1; k <= n; ++k) c[j][k] = (c[j][k] - 1ll * c[i][k] * res % P + P) % P;
			b[j] = (b[j] - 1ll * b[i] * res % P + P) % P;
		}
		if (fa[fa[i]]) {
			int j = fa[fa[i]], res = 1ll * c[j][i] * pow(c[i][i], P - 2) % P;
			for (int k = 1; k <= n; ++k) c[j][k] = (c[j][k] - 1ll * c[i][k] * res % P + P) % P;
			b[j] = (b[j] - 1ll * b[i] * res % P + P) % P;
		}
	}
	for (int i = 1; i <= n; ++i) {
		for (int l = st[i]; l; l = nx[l]) if (to[l] != fa[i] && to[l] != fa[fa[i]]) {
			int j = to[l], res = 1ll * c[j][i] * pow(c[i][i], P - 2) % P;
			for (int k = 1; k <= n; ++k) c[j][k] = (c[j][k] - 1ll * c[i][k] * res % P + P) % P;
			b[j] = (b[j] - 1ll * b[i] * res % P + P) % P;
		}
	}
	for (int i = 1; i <= n; ++i) printf("%d\n", 1ll * b[i] * pow(c[i][i], P - 2) % P);
	return 0;
}

作者:zjlcnblogs

出处:https://www.cnblogs.com/zjlcnblogs/p/11733396.html

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

posted @ 2019-10-24 17:08  gz-gary  阅读(224)  评论(0)    收藏  举报
编辑推荐:
· 大数据高并发核心场景实战,数据持久化之冷热分离
· 运维排查 | SaltStack 远程命令执行中文乱码问题
· Java线程池详解:高效并发编程的核心利器
· 从“看懂世界”到“改造世界”:AI发展的四个阶段你了解了吗?
· 协程本质是函数加状态机——零基础深入浅出 C++20 协程
阅读排行:
· .NET 8 gRPC 实现高效100G大文件断点续传工具
· STM32学会要花费多长时间?一个从机械转行老程序员的血泪史
· LinqPad:C#代码测试学习一品神器
· .NET入行第4年(第二集)
· C#/.NET/.NET Core技术前沿周刊 | 第 43 期(2025年6.16-6.22)
more_horiz
keyboard_arrow_up dark_mode palette
选择主题
点击右上角即可分享
微信分享提示