29 HNOI2011 XOR和路径 题解
XOR和路径
题面
给定一个 \(N\) 个点,\(M\) 条边的无向连通图,其边的权值为非负整数。
该路径可以重复经过某些节点或边,当一条边在路径中出现多次时,其权值在计算 XOR 和时也应被重复计算相应多的次数。
具体来说,从 1 号节点开始,以相等的概率,随机选择与当前节点相关联的某条边,并沿着这条边走到下一个节点,重复这个过程直到走到 N 号节点为止,便得到一条从 1 号节点到 N 号节点的路径。
显然得到每条这样的路径的概率是不同的,并且每条这样的路径的 XOR 和也不一样。
现在请你求出该算法得到的路径的 XOR 和的期望值。
题解
考虑dp求解,设 \(f(x)\) 表示从 \(x\) 节点走到 \(N\) 节点的XOR和期望值,但是发现这个状态的转移方程好像写不出来,因为期望值可能是个小数,那你一个小数异或是没有意义的
考虑异或的性质:各个位互不影响
所以我们可以按位考虑最后的异或和,\(f(x)\) 表示从 \(x\) 节点走到 \(N\) 节点的XOR和第 \(k\) 位为 1 的概率
这里我们采用倒推的方式,就是实际上我们是 \(u \to v\) ,但方程中是从 \(v\) 转移到 \(u\)
转移方程:
\[\begin {align}
&f_u = \frac 1 {deg_u} [ \sum_{e_i = 0} f_v + \sum_{e_i = 1} (1 - f_v)] \\
°_u \times f_u - \sum_{e_i = 0} f_v + \sum_{e_i = 1} f_v = \sum_{e_i = 1} 1
\end {align}
\]
这里的 \(e_i\) 表示 \((u,v)\) 的边权 \(\& (1 << k)\) 的值
要注意的一点是,\(f_n = 0\) 这是已知的,所以我们只有 \(n - 1\) 个未知数以及方程,如果某个 \(v = n\) 的话,要特判一下
然后对于每一位都算一遍,最后按照每一位的概率乘上相对应的值即为最后答案
这题还有一点细节,图中可能有自环,此时这条边不能加两次,因为题目中有 “随机选择与当前节点相关联的某条边” 所以要特殊处理一下
时间复杂度为 \(O(30n^3)\)
code
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <vector>
using namespace std;
const int N = 110;
int n, m;
vector <pair <int, int> > e[N];
double a[N][N];
int deg[N];
//高斯消元
void Guss () {
for (int i = 1; i < n; i ++) {
int mx = i;
for (int j = i + 1; j < n; j ++) {
if (abs (a[j][i]) > abs (a[mx][i])) {
mx = j;
}
}
if (mx != i) swap (a[mx], a[i]);
for (int j = 1; j < n; j ++) {
if (i == j) continue;
double mul = a[j][i] / a[i][i];
for (int k = 1; k <= n; k ++) {
a[j][k] -= a[i][k] * mul;
}
}
}
}
int main () {
cin >> n >> m;
for (int i = 1; i <= m; i ++) {
int x, y, z;
cin >> x >> y >> z;
//特殊处理自环的情况
if (x == y) {
e[x].push_back ({y, z});
deg[x] ++;
} else {
e[x].push_back ({y, z});
e[y].push_back ({x, z});
deg[x] ++, deg[y] ++;
}
}
double ans = 0;
for (int i = 0; i <= 30; i ++) {
memset (a, 0, sizeof a);
for (int u = 1; u < n; u ++) {
a[u][u] = deg[u];
for (auto p : e[u]) {
int v = p.first, val = (p.second >> i) & 1;
if (v == n) {
if (val) a[u][n] ++;
} else {
if (val) {
a[u][n] ++;
a[u][v] ++;
} else {
a[u][v] --;
}
}
}
}
Guss ();
ans += (a[1][n] / a[1][1]) * (1 << i);
}
printf ("%.3lf\n", ans);
return 0;
}