bzoj 2337: [HNOI2011]XOR和路径
Description
给定一个无向连通图,其节点编号为 1 到 N,其边的权值为非负整数。试求出一条从 1 号节点到 N 号节点的路径,使得该路径上经过的边的权值的“XOR 和”最大。该路径可以重复经过某些节点或边,当一条边在路径中出现多次时,其权值在计算“XOR 和”时也要被重复计算相应多的次数。
直接求解上述问题比较困难,于是你决定使用非完美算法。具体来说,从 1 号节点开始,以相等的概率,随机选择与当前节点相关联的某条边,并沿这条边走到下一个节点,重复这个过程,直到走到 N 号节点为止,便得到一条从 1 号节点到 N 号节点的路径。显然得到每条这样的路径的概率是不同的并且每条这样的路径的“XOR 和”也不一样。现在请你求出该算法得到的路径的“XOR 和”的期望值。
solution
正解:高斯消元
根据期望的线性性,要求的东西可以拆成每一位二进制位的期望之和
考虑从i到达n时该二进制位为1的方案,分边权是否为1讨论:
\(f[j]+=f[i]/du[i]\) 如果边权该位为0
\(f[j]+=(1-f[i])/du[i]\) 如果边权的该位为1
考虑到这是有向图,且存在环,所以不能拓扑序DP,用高斯消元可以解:
我们把 f[1],f[2]...f[n]看做是变量,然后du[i]看做是系数,就可以求了
但是做出来是WA的?一看题解仿佛必须要逆向推啊?原因仿佛是正推会存在不能走到i的概率?
#include <algorithm>
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <cmath>
#define RG register
#define il inline
#define iter iterator
#define Max(a,b) ((a)>(b)?(a):(b))
#define Min(a,b) ((a)<(b)?(a):(b))
using namespace std;
typedef long long ll;
const int N=105,M=20005;
int n,m,num=0,du[N],head[M],to[M],dis[M],nxt[M];
void link(int x,int y,int z){
nxt[++num]=head[x];to[num]=y;head[x]=num;dis[num]=z;}
double a[N][N],ans=0;
inline double solve(){
for(int l=1;l<=n;l++){
int maxid=l;
for(int i=l+1;i<=n;i++)
if(fabs(a[i][l])>fabs(a[maxid][l]))maxid=l;
if(l!=maxid)swap(a[l],a[maxid]);
for(int i=l+1;i<=n;i++){
if(!a[i][l])continue;
double tmp=a[l][l]/a[i][l];
for(int j=l;j<=n+1;j++)
a[i][j]=a[l][j]-tmp*a[i][j];
}
}
for(int i=n;i>=1;i--){
for(int j=i+1;j<=n;j++)
a[i][n+1]-=a[i][j]*a[j][n+1];
a[i][n+1]/=a[i][i];
}
return a[1][n+1];
}
void work()
{
int x,y,z;
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
scanf("%d%d%d",&x,&y,&z);
link(x,y,z);du[y]++;
if(x!=y)link(y,x,z),du[x]++;
}
for(int w=0;w<=30;w++){
memset(a,0,sizeof(a));
for(int i=1;i<n;i++){
a[i][i]=1.0;
for(int j=head[i];j;j=nxt[j]){
int u=to[j];
if(dis[j]&(1<<w))
a[i][u]+=1.0/du[i],a[i][n+1]+=1.0/du[i];
else a[i][u]-=1.0/du[i];
}
}
a[n][n]=1.0;
ans+=solve()*(1<<w);
}
printf("%.3lf\n",ans);
}
int main(){
work();
return 0;
}