[HNOI 2011]XOR和路径
Description
给定一个无向连通图,其节点编号为 1 到 N,其边的权值为非负整数。试求出一条从 1 号节点到 N 号节点的路径,使得该路径上经过的边的权值的“XOR 和”最大。该路径可以重复经过某些节点或边,当一条边在路径中出现多次时,其权值在计算“XOR 和”时也要被重复计算相应多的次数。
直接求解上述问题比较困难,于是你决定使用非完美算法。具体来说,从 1 号节点开始,以相等的概率,随机选择与当前节点相关联的某条边,并沿这条边走到下一个节点,重复这个过程,直到走到 N 号节点为止,便得到一条从 1 号节点到 N 号节点的路径。显然得到每条这样的路径的概率是不同的并且每条这样的路径的“XOR 和”也不一样。现在请你求出该算法得到的路径的“XOR 和”的期望值。
Input
从文件input.txt中读入数据,输入文件的第一行是用空格隔开的两个正整数N和M,分别表示该图的节点数和边数。紧接着的M行,每行是用空格隔开的三个非负整数u,v和w(1≤u,v≤N,0≤w≤109),表示该图的一条边(u,v),其权值为w。输入的数据保证图连通,30%的数据满足N≤30,100%的数据满足2≤N≤100,M≤10000,但是图中可能有重边或自环。
Output
输出文件 output.txt 仅包含一个实数,表示上述算法得到的路径的“XOR 和”的期望值,要求保留三位小数。(建议使用精度较高的数据类型进行计算)
Sample Input
2 2
1 1 2
1 2 3
Sample Output
2.333
HINT
样例解释:有1/2的概率直接从1号节点走到2号节点,该路径的“XOR和”为3;有1/4的概率从1号节点走一次1号节点的自环后走到2号节点,该路径的“XOR和”为1;有1/8的概率从1号节点走两次1号节点的自环后走到2号节点,该路径的“XOR和”为3;„„;依此类推,可知“XOR和”的期望值为:3/2+1/4+3/8+1/16+3/32+„„=7/3,约等于2.333。
题解
首先看到路径$xor$值,还是选择按位做。
我们设$f_u$表示从$u$到$n$的路径异或值为$1$的概率。显然$f_n == 0$。
此外,设$w(u, v)$为$u->v$的边权($1/0$),那么有:
$$f_u = \sum_{(u,v) \in E,\ w(u,v) = 0} \frac{f_v}{degree_u} + \sum_{(u,v) \in E,\ w(u,v) = 1} \frac{1-f_v}{degree_u}$$
那么我们可以得到$n$个方程,用高斯消元求解。
可以乘上$degree_u$减小误差。
这题特殊说明一下为什么不能顺推而要逆推:
很多题解的说法是因为“如果正推的话,$1−f_i$代表的不仅从$1$到$i$异或和不为$1$的概率,还包含了从$1$不走到$i$的概率,无法转移”。
如果这样解释,那就解释不了$i$走不到$n$的情况。
我认为合理的解答是:因为$1$可以重复走多次,而$n$只能走$1$次。
1 //It is made by Awson on 2017.10.21 2 #include <set> 3 #include <map> 4 #include <cmath> 5 #include <ctime> 6 #include <stack> 7 #include <queue> 8 #include <vector> 9 #include <string> 10 #include <cstdio> 11 #include <cstdlib> 12 #include <cstring> 13 #include <iostream> 14 #include <algorithm> 15 #define LL long long 16 #define Min(a, b) ((a) < (b) ? (a) : (b)) 17 #define Max(a, b) ((a) > (b) ? (a) : (b)) 18 #define Abs(x) ((x) < 0 ? (-(x)) : (x)) 19 using namespace std; 20 const int N = 100; 21 const int M = 10000; 22 int st[32]; 23 24 int n, m, u, v, w; 25 struct tt { 26 int to, cost, next; 27 }edge[(M<<1)+5]; 28 int path[N+5], top, degree[N+5]; 29 double A[N+5][N+5], ans; 30 31 double Gauss() { 32 for (int line = 1; line <= n; line++) { 33 int max_line = line; 34 for (int i = line+1; i <= n; i++) if (fabs(A[i][line]) > fabs(A[max_line][line])) max_line = i; 35 if (max_line != line) swap(A[line], A[max_line]); 36 for (int i = line+1; i <= n; i++) { 37 double div = A[i][line]/A[line][line]; 38 for (int j = line; j <= n+1; j++) A[i][j] -= A[line][j]*div; 39 } 40 } 41 for (int i = n; i >= 1; i--) { 42 for (int j = i+1; j <= n; j++) 43 A[i][n+1] -= A[i][j]*A[j][n+1]; 44 A[i][n+1] /= A[i][i]; 45 } 46 return A[1][n+1]; 47 } 48 void add(int u, int v, int w) { 49 edge[++top].to = v; 50 edge[top].cost = w; 51 edge[top].next = path[u]; 52 path[u] = top; degree[v]++; 53 } 54 void work() { 55 st[0] = 1; for (int i = 1; i <= 30; i++) st[i] = st[i-1]<<1; 56 scanf("%d%d", &n, &m); 57 for (int i = 1; i <= m; i++) { 58 scanf("%d%d%d", &u, &v, &w); 59 add(u, v, w); if (u != v) add(v, u, w); 60 } 61 for (int i = 0; i <= 30; i++) { 62 memset(A, 0, sizeof(A)); 63 for (int u = 1; u < n; u++) { 64 A[u][u] = degree[u]; 65 for (int j = path[u]; j; j = edge[j].next) { 66 if (st[i]&edge[j].cost) A[u][edge[j].to] += 1., A[u][n+1] += 1.; 67 else A[u][edge[j].to] -= 1.; 68 } 69 } 70 A[n][n] = 1; 71 ans += Gauss()*(double)st[i]; 72 } 73 printf("%.3lf\n", ans); 74 } 75 int main() { 76 work(); 77 return 0; 78 }