[HNOI2011]XOR和路径
题目描述
给定一个无向连通图,其节点编号为 1 到 N,其边的权值为非负整数。试求出一条从 1 号节点到 N 号节点的路径,使得该路径上经过的边的权值的“XOR 和”最大。该路径可以重复经过某些节点或边,当一条边在路径中出现多次时,其权值在计算“XOR 和”时也要被重复计算相应多的次数。
直接求解上述问题比较困难,于是你决定使用非完美算法。具体来说,从 1 号节点开始,以相等的概率,随机选择与当前节点相关联的某条边,并沿这条边走到下一个节点,重复这个过程,直到走到 N 号节点为止,便得到一条从 1 号节点到 N 号节点的路径。显然得到每条这样的路径的概率是不同的并且每条这样的路径的“XOR 和”也不一样。现在请你求出该算法得到的路径的“XOR 和”的期望值。
输入输出格式
输入格式:
从文件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.txt 仅包含一个实数,表示上述算法得到的路径的“XOR 和”的期望值,要求保留三位小数。(建议使用精度较高的数据类型进行计算)
输入输出样例
输入样例#1:
2 2
1 1 2
1 2 3
输出样例#1:
2.333
说明
样例解释:有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。
显然是概率题
然后想了半天也没想出啥东西还当成高斯消元解异或方程组搞了半天==
然后就开心的看题解了
对于这道题我们可以考虑按位处理
因为每一位都互不影响
我们枚举每一位
用 f[i] 表示点i在这一位是1的概率
显然点i在这一位是0的概率就是 1 - f[i]
d[i]表示点i的度
如果v ~ u的这条边在这一位是1
那就要用(1-f[v])(点v在这一位是0的概率)转移才能使点u在这一位是1
在v~u的这条边是0同理
这样就可以写出dp式子 \(f[i] = \frac{1}{d[i]}(\sum_{edge(u,v)=0}f[v]+\sum_{edge(u,v)=1}{1-f[v]})\)
显然有后效性,不能直接递推
那就把式子化成这样 :
\(d[i]*f[i]-\sum_{edge(u,v)=0}{f[v]}+\sum_{edge(u,v)=1}{f[v]}=\sum_{edge(u,v)=1}{1}\)
这样就可以高斯消元了对吧
然后还要注意两点
1.如果有自环就只要连一条边(因为自环走两次相当于没走)
2.矩阵的第n行只有(n,n)是1,其他列都是0,其他行的第n列都是0(因为走到n就结束了,不能通过n来转移)
#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
const int M = 105 ;
const int N = 10005 ;
const double eps = 1e-7 ;
using namespace std ;
inline int read() {
char c = getchar() ; int x = 0 , w = 1 ;
while(c>'9'||c<'0') { if(c=='-') w = -1 ; c = getchar() ; }
while(c>='0'&&c<='9') { x = x*10+c-'0' ; c = getchar() ; }
return x*w ;
}
int n , m ;
struct E {
int Nxt , to , dis ;
}edge[N<<1];
int hea[M] , num ;
inline void add_edge(int from , int to , int dis) {
edge[++num].Nxt = hea[from] ;
edge[num].to = to ;
edge[num].dis = dis ;
hea[from] = num ;
}
int d[M] , Mx ;
double B[M][M] , Ans ;
inline void Solve(int x) {
memset(B , 0 , sizeof(B)) ;
for(int i = 1 ; i < n ; i ++) {
B[i][i] = d[i] ;
for(int j = hea[i] ; j ; j = edge[j].Nxt) {
int v = edge[j].to ;
if(edge[j].dis&(1<<(x - 1)))
++B[i][v] , ++B[i][n + 1] ;
else --B[i][v] ;
}
}
for(int i = 1 ; i < n ; i ++) B[i][n] = 0 ;
B[n][n] = 1 ;
}
inline void Gauss() {
for(int i = 1 , Tab ; i <= n ; i ++) {
Tab = i ;
for(int j = i + 1 ; j <= n ; j ++)
if(fabs(fabs(B[j][i]) - fabs(B[Tab][i])) <= eps)
Tab = j ;
if(Tab != i)
for(int j = 1 ; j <= n + 1 ; j ++) swap(B[i][j] , B[Tab][j]) ;
for(int j = i + 1 ; j <= n + 1 ; j ++) B[i][j] /= B[i][i] ;
for(int j = 1 ; j <= n ; j ++)
if(i != j)
for(int k = i + 1 ; k <= n + 1 ; k ++)
B[j][k] -= B[j][i] * B[i][k] ;
}
}
int main() {
n = read() ; m = read() ;
for(int i = 1 , u , v , w ; i <= m ; i ++) {
u = read() , v = read() , w = read() ;
Mx = max(Mx , w) ;
add_edge(u , v , w) ; ++d[u] ;
if(u ^ v) add_edge(v , u , w) , ++d[v] ;
}
for(int i = 1 ; i <= 31 ; i ++) {
if((1<<(i - 1)) > Mx) break ;
Solve(i) ; Gauss() ;
Ans += (1<<(i - 1)) * B[1][n + 1] ;
}
printf("%.3lf\n",Ans) ;
return 0 ;
}