洛谷 P3211 [HNOI2011] XOR和路径

题目传送门


思路

哇,概率期望!
哇,图上游走!
哇,\(n \leq 100\)
高斯消元没跑了。

1. 状态与转移

求期望一般用倒推,设 \(f_u\) 表示从 \(u\) 节点到 \(n\) 节点的异或路径和的期望。
直接列方程:

\[f_u = \frac{1}{d_u} \sum_{(u, v) \in E} w_{u, v} \bigoplus f_v \]

其中 \(d_u\) 就表示 \(u\) 节点的度。
整个方程的含义就是从 \(u\) 节点出发,会有 \(\frac{1}{d_u}\) 的概率走到相邻的一个节点 \(v\)。因为从 \(u\) 走到 \(v\) 再走到 \(n\) 的异或期望为 \(w_{u, v} \bigoplus f_v\),所以此种情况的贡献就是 \(\frac{1}{d_u} \times (w_{u, v} \bigoplus f_v)\)

由于有位运算,而且还是与浮点数 \(f\) 进行位运算,好像不太能转化成高斯消元的样子。。。

难道要燃尽了吗?

经过一番学习 (看题解) 之后,终于意识到期望是可以进行线性运算,即:

\[E(X + Y) = E(X) + E(Y), \]

\[E(X + c) = E(X) + c, \]

\[E(k \times x) = k \times E(x), \]

\[E(aX + bY) = aE(x) + bE(y). \]

其中 \(X,Y\) 是两个时间的结果大小,\(k, a, b, c\) 均为常数

由于位运算时,每一位都是互不干扰的,所以总的期望就可以通过线性期望来拆解
因此,我们对边的每一位进行一次高斯消元,第 \(i\) 次消元的每条边的边权就是 \(w \& (1 << i)\)
我们设 \(f_u\) 表示从 \(u\) 节点走到 \(n\) 节点时,异或和为 \(1\) 的期望(其实就是概率),那就可以列出转移方程:

\[f_u = \frac{1}{d_u} [\sum_{w_{u, v} \& (1<<i) = 0} f_v + \sum_{w_{u, v} \& (1<<i) = 1} (1-f_v)] \]

意思如下:

  1. 如果该边边权为 \(0\),那么走过去我的异或和就不会改变,该边对 \(u\) 节点的贡献就是【\(v\)\(n\) 节点的期望 \(\times \frac{1}{d_u}\)】;
  2. 如果该边边权为 \(1\),那么走过去我的异或和就会改变,那么由于 \(v\)\(n\) 节点异或和为 \(1\) 的概率为 \(f_v\),那么它到 \(n\) 的异或和为 \(0\) 的概率为 \(1 - f_v\),走过这条边后,\(0\)\(1\)\(1\)\(0\),概率反转,\(u\) 走到 \(n\) 异或和概率为 \(1 - f_v\),再乘以 \(\frac{1}{d_u}\) 就是这条变的贡献。

最后将其化为高斯消元的形式即可。

2. 答案

\[ans = \sum_{i = 0}^{30} 2^i \times f_1 \]

3.复杂度

  1. 时间:有系数矩阵的预处理,所以是 \(O(30 \times (n \times m + n ^ 3))\)
  2. 空间:\(O(n^2)\)

代码

#include <bits/stdc++.h>

#define mkpr make_pair
#define fir first
#define sec second

using namespace std;

typedef pair<int, int> pii;

const int maxn = 1e2 + 7;

int n, m;
double a[maxn][maxn];
vector<pii> e[maxn];

// 预处理系数矩阵
void init(int pos) {
	memset(a, 0, sizeof(a));
	for (int u = 1; u < n; ++u) {
		a[u][u] = e[u].size();
		for (pii ed : e[u]) {
			int v = ed.fir, w = ed.sec;
			if (w & (1 << pos))
				a[u][v] += 1.0, a[u][n + 1] += 1.0;
			else a[u][v] -= 1.0;
		}
	}
	a[n][n] = 1;
}
void Guass_Jordan() {
	for (int r = 1, c = 1; c <= n; ++r) {
		int maxr = r;
		for (int i = r + 1; i <= n; ++i)
			if (fabs(a[maxr][c]) < fabs(a[i][c])) maxr = i;
		if (maxr != r) swap(a[maxr], a[r]);
		if (fabs(a[r][c]) < 1e-8) {--r, ++c; continue;}
		
		for (int j = n + 1; j >= c; --j)
			a[r][j] /= a[r][c];
		for (int i = 1; i <= n; ++i) {
			if (i == r) continue;
			for (int j = n + 1; j >= c; --j)
				a[i][j] -= a[r][j] * a[i][c];
		}
		++c;
//		puts("----------------------------------------");
//		for (int i = 1; i <= n; ++i, printf("\n"))
//			for (int j = 1; j <= n + 1; ++j)
//				printf("%.6lf ", a[i][j]);
	}
}
double ans;
int main() {
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= m; ++i) {
		int u, v, w;
		scanf("%d%d%d", &u, &v, &w); 
		e[u].push_back(mkpr(v, w));
		if (u != v) e[v].push_back(mkpr(u, w));
	} 
	for (int i = 0; i <= 30; ++i) {
		init(i);
//		puts("===========================================");
//		printf("i: %d\n", i + 1);
//		for (int j = 1; j <= n; ++j, printf("\n"))
//			for (int k = 1; k <= n + 1; ++k)
//				printf("%.6lf ", a[j][k]);
		Guass_Jordan();
		ans += (1 << i) * a[1][n + 1];
//		printf("f1 = %.6lf\n", a[1][n + 1]);
	}
	printf("%.3lf\n", ans);
	return 0;
} 
posted @ 2025-05-17 08:21  syzyc  阅读(13)  评论(0)    收藏  举报