HNOI2011 XOR和路径 高斯消元+期望

传送门

题目大意:给定一张无向连通图,每次有相等概率走一条边,求1号点到n号点异或和的期望值。

这道题和NOI2005聪聪与可可很像,只不过…聪聪与可可那题猫一定会抓住老鼠,而这题有环,那么就不能用记忆化搜索了,因为每个点的状态都可以互相更新。

但这些不断更新的状态最终的答案都会趋近于某一个值,而求得这个值的方法也就是高斯消元。通过解方程的思想,来得到那个值。

题目求的是异或,而异或运算中各个二进制位的答案是互不影响的,于是我们可以统计每一位是1的期望,累加即为答案。

对于枚举的这一位,设 d p [ u ] dp[u] dp[u]表示u->n该位异或和为1的期望。

先列出状态转移方程:

d p [ u ] = 1 d u [ u ] ( ∑ l e n ( u , v ) = 1 d p [ v ] + ∑ l e n ( u , v ) = 0 ( 1 − d p [ v ] ) ) dp[u]=\frac{1}{du[u]}(\sum_{len(u,v)=1}{dp[v]}+\sum_{len(u,v)=0}{(1-dp[v])}) dp[u]=du[u]1(len(u,v)=1dp[v]+len(u,v)=0(1dp[v]))

两边同时乘上 1 d u [ u ] \frac{1}{du[u]} du[u]1,得

d u [ u ] ∗ d p [ u ] = ∑ l e n ( u , v ) = 1 d p [ v ] + ∑ l e n ( u , v ) = 0 ( 1 − d p [ v ] ) du[u]*dp[u]=\sum_{len(u,v)=1}{dp[v]}+\sum_{len(u,v)=0}{(1-dp[v])} du[u]dp[u]=len(u,v)=1dp[v]+len(u,v)=0(1dp[v])

转化得

d u [ u ] ∗ d p [ u ] = ∑ l e n ( u , v ) = 1 d p [ v ] + ∑ l e n ( u , v ) = 0 1 − ∑ l e n ( u , v ) = 0 d p [ v ] du[u]*dp[u]=\sum_{len(u,v)=1}{dp[v]}+\sum_{len(u,v)=0}{1}-\sum_{len(u,v)=0}{dp[v]} du[u]dp[u]=len(u,v)=1dp[v]+len(u,v)=01len(u,v)=0dp[v]

则有

d u [ u ] ∗ d p [ u ] + ∑ l e n ( u , v ) = 0 d p [ v ] − ∑ l e n ( u , v ) = 1 d p [ v ] = ∑ l e n ( u , v ) = 0 1 du[u]*dp[u]+\sum_{len(u,v)=0}{dp[v]}-\sum_{len(u,v)=1}{dp[v]}=\sum_{len(u,v)=0}{1} du[u]dp[u]+len(u,v)=0dp[v]len(u,v)=1dp[v]=len(u,v)=01

突然舒服.jpg
然后就套高斯消元模板了

坑:自环只建一条边!!!

#include
#include
#include
#include
using namespace std;
const int MAXN = 110;
const int MAXM = 10010;
const double eps = 1e-7;

int n, m;
int du[MAXN], fir[MAXN], nxt[MAXM << 1], to[MAXM << 1], len[MAXM << 1], cnt;
double ele[MAXN][MAXN], ans[MAXN];

inline int read(){
	int k = 0; char ch = getchar();
	while(ch < '0' || ch > '9') ch = getchar();
	while(ch >= '0' && ch <= '9') k = k*10 + ch - '0', ch = getchar();
	return k;
}

inline void add_edge(int a, int b, int l){
	du[a]++; len[cnt] = l; to[cnt] = b; nxt[cnt] = fir[a]; fir[a] = cnt++;
}

double gauss(){
//	for(int i = 1; i <= n; i++){
//		for(int j = 1; j <= n + 1; j++){
//			printf("%.2lf ", ele[i][j]);
//		}
//		printf("\n");
//	}
	for(int i = 1; i <= n; i++){ //消xi
		int maxn = i;
		for(int j = i + 1; j <= n; j++)
			if(fabs(ele[j][i]) > fabs(ele[maxn][i]))
				maxn = j;
		
		if(fabs(ele[maxn][i]) < eps) return 0;
		if(maxn != i) swap(ele[i], ele[maxn]);
		
		double div = ele[i][i];
		for(int j = 1; j <= n + 1; j++)
			ele[i][j] /= div;
		
		for(int j = i + 1; j <= n; j++){
			div = ele[j][i];
			for(int k = i; k <= n + 1; k++){
				ele[j][k] -= div * ele[i][k];
			}
		}
	}
	ans[n] = ele[n][n + 1];
	for(int i = n - 1; i >= 1; i--){
		for(int j = 1; j <= i; j++){
			ele[j][n + 1] -= ele[j][i + 1] * ans[i + 1];
		}
		ans[i] = ele[i][n + 1];
	}
//	for(int i = 1; i <= n; i++){
//		printf("%.2lf\n", ans[i]);
//	}
	return ans[1];
}

void build(int t){
	memset(ele, 0, sizeof(ele));
	ele[n][n] = 1;
	for(int u = 1; u <= n - 1; u++){
		ele[u][u] = du[u];
		for(int i = fir[u]; i != -1; i = nxt[i]){
			int v = to[i];
			if(len[i] & t){
				ele[u][v] += 1.0;
				ele[u][n + 1] += 1.0;
			}
			else{
				ele[u][v] -= 1.0;
			}
		}
	}
}

int main(){
	memset(fir, -1, sizeof(fir));
	n = read(), m = read();
	int R = 0;
	for(int i = 1; i <= m; i++){
		int a = read(), b = read(), l = read();
		add_edge(a, b, l);
		if(a != b) add_edge(b, a, l); //坑点!自环只能加一条边 
		R = max(R, l);
	}
	
	double Ans = 0;
	for(int i = 1; i <= R; i <<= 1){
		build(i);
		Ans += gauss() * i;
	}
	printf("%.3lf", Ans);
	return 0;
}

你可能感兴趣的:(期望,高斯消元)