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;
}