[HNOI2011]XOR和路径——高斯消元、随机游走模型

[HNOI2011]XOR和路径

思路

拆位来看,边的权值就变为了只有0和1,而且只有经过权值为1的边的次数为奇数次时会对答案有贡献,所以我们只需算到达节点 n 时经过奇数次权值为1的边的概率即可。

先设 P i P_i Pi 表示从1走到 n 中经过点 i 的概率,那么应该有转移关系式
P i = ∑ ( i , v ) ∈ E , v ≠ n P v d v P_i=\sum_{(i,v)\in E,v\neq n}\frac{P_v}{d_v} Pi=(i,v)E,v=ndvPv
其中 d v d_v dv 表示节点 v 的度数,这里的边不考虑边权。

在推概率关系的时候我们会发现一个问题,即节点 n 必须从相邻的点过来,节点 2~n-1 必须从除了n以外的相邻点过来,而节点1既可以在初始位置,又可以从不为n的相邻点过来。这样一来,应该满足 P 1 = 1 P_1=1 P1=1 ,即 P 1 P_1 P1 不满足上面的关系式。如果不满足转移关系式,不就相当于从1走出后不能走回来了吗?

所以为了解决这个问题,我们假设1不为起点,另设一个起点0。起点0可以走到与1相邻的节点但不能走回来,节点1只能从相邻节点走过来。这样拆开后只有 P 0 = 1 P_0=1 P0=1 固定,其它的都可以转移了。

然而我们发现这个转移有环,不能直接枚举点转移,但是无论有无环都满足关系式。所以我们把关系式列出来,就得到了一个完整的 n 元一次方程组,用高斯消元求解即可。

把这个 P i P_i Pi 搞到手后就好办了。设 p i p_i pi 表示走到节点 i 时经过奇数次权值为1的边的概率,那么经过偶数次的概率就为 P i − p i P_i-p_i Pipi 。如果我们不先求出经过每个点的概率,而直接把经过偶数次的概率也设出来求,那么复杂度就是 O ( 8 n 3 log ⁡ w ) ≈ O ( 240000000 ) O(8n^3\log w)≈O(240000000) O(8n3logw)O(240000000) ,再加上高斯消元的大常数肯定过不了。先求出 P i P_i Pi 可以把未知数个数缩为一半,时间缩为 1 8 \frac{1}{8} 81

显然有 p 0 = 0 p_0=0 p0=0 ,然后当 i > 0 i>0 i>0 时, p i p_i pi 满足以下关系
p i = ∑ ( i , v , 1 ) ∈ E , v ≠ n P v − p v d v + ∑ ( i , v , 0 ) ∈ E , v ≠ n p v d v p_i=\sum_{(i,v,1)\in E,v\neq n}\frac{P_v-p_v}{d_v} +\sum_{(i,v,0)\in E,v\neq n}\frac{p_v}{d_v} pi=(i,v,1)E,v=ndvPvpv+(i,v,0)E,v=ndvpv
同样用高斯消元求解即可。因为 P i P_i Pi 无关边权,所以只用在最开始求一次。总复杂度为 O ( n 3 log ⁡ w ) O(n^3\log w) O(n3logw)


事后把数据输出发现, P i P_i Pi 居然很多都大于1。

P1: 1.800
P2: 2.400
P3: 2.000
P4: 1.800
P5: 0.800
...

(看起来真不错)
这是怎么回事呢?反正我A了,管它呢,不知道。

有可能我定义的根本就不是概率,
有可能高斯消元产生了精度问题(?上千倍的精度误差?),
有可能我两边求概率的方法都错了、凑一起就对了,
还有可能是由于我经常虔诚地膜拜 JZM ,老天放我一马…

代码

47ms rank1代码(还可优化)

#include<cstdio>//JZM yyds!!
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<vector>
#include<queue>
#include<stack>
#include<ctime>
#include<set>
#define ll long long
#define MAXN 105
#define uns unsigned
#define INF 1e17
#define MOD 998244353ll
#define lowbit(x) ((x)&(-(x)))
using namespace std;
inline ll read(){
	ll x=0;bool f=1;char s=getchar();
	while((s<'0'||s>'9')&&s>0){if(s=='-')f^=1;s=getchar();}
	while(s>='0'&&s<='9')x=(x<<1)+(x<<3)+s-'0',s=getchar();
	return f?x:-x;
}
const double MN=1e-13;
int n,m;
struct edge{
	int v,w;edge(){}
	edge(int V,int W){v=V,w=W;}
};
vector<edge>G[MAXN];
double p[MAXN];
double c[MAXN][MAXN],x[MAXN];
inline int Gauss(int n,int m,int&tm){
	for(int i=0;i<=m;i++)x[i]=0;
	int col,row;
	for(row=0,col=0;row<n&&col<m;row++,col++){
		int mxr=row;
		for(int i=row+1;i<n;i++)
			if(fabs(c[i][col])>fabs(c[mxr][col]))mxr=i;
		if(mxr!=row&&(++tm))
			for(int j=col;j<=m;j++)swap(c[row][j],c[mxr][j]);
		if(fabs(c[row][col])<MN){row--;continue;}
		for(int i=row+1;i<n;i++){
			if(fabs(c[i][col])<MN)continue;
			double g=c[i][col]/c[row][col];
			c[i][col]=0;
			for(int j=col+1;j<=m;j++)c[i][j]-=c[row][j]*g;
		}
	}
	for(int i=row;i<n;i++)//无解 
		if(fabs(c[i][col])>=MN)return -1;
	if(row<m)return m-row;//无数解 
	for(int i=n-1;i>=0;i--){//唯一解 
		double b=c[i][m];
		for(int j=i+1;j<m;j++)b-=c[i][j]*x[j];
		x[i]=b/c[i][i];
	}
	return 0;
}
signed main()
{
	n=read(),m=read();
	int mx=0;
	for(int i=1;i<=m;i++){
		int u=read(),v=read(),w=read();
		G[u].push_back(edge(v,w)),mx=max(mx,w);
		if(v!=u)G[v].push_back(edge(u,w));
	}
	c[0][0]=c[0][n+1]=1;
	for(int i=1;i<=n;i++){
		c[i][i]=1;
		for(uns j=0;j<G[i].size();j++){
			int v=G[i][j].v;
			if(v==n)continue;
			c[i][v]-=1.0/G[v].size();
			if(v==1)c[i][0]-=1.0/G[v].size();
		}
	}int tm=0;
	Gauss(n+1,n+1,tm);
	for(int i=0;i<=n;i++)p[i]=x[i];
	double ans=0;
	for(int D=0;D<30&&(1<<D)<=mx;D++){
		for(int i=n+1;i>=0;i--)
			for(int j=n+1;j>=0;j--)c[i][j]=0;
		for(int i=n+1;i>=0;i--)x[i]=0;
		c[0][0]=1,c[0][n+1]=0;
		for(int u=1;u<=n;u++){
			c[u][u]=1;
			for(uns i=0;i<G[u].size();i++){
				int v=G[u][i].v,w=G[u][i].w;
				if(v==n)continue;
				if((w>>D)&1)c[u][v]+=1.0/G[v].size(),
				c[u][n+1]+=p[v]/G[v].size();
				else c[u][v]-=1.0/G[v].size();
				if(v==1){
					if((w>>D)&1)c[u][0]+=1.0/G[v].size(),
					c[u][n+1]+=p[0]/G[v].size();
					else c[u][0]-=1.0/G[v].size();
				}
			}
		}
		Gauss(n+1,n+1,tm=0);
		ans+=x[n]*(1<<D);
	}
	printf("%.3f\n",ans);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值