传送门
题目大意:给定一张无向连通图,每次有相等概率走一条边,求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)=1∑dp[v]+len(u,v)=0∑(1−dp[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)=1∑dp[v]+len(u,v)=0∑(1−dp[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)=1∑dp[v]+len(u,v)=0∑1−len(u,v)=0∑dp[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)=0∑dp[v]−len(u,v)=1∑dp[v]=len(u,v)=0∑1
突然舒服.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;
}