bzoj 3143: [Hnoi2013]游走
题目链接
题解
代码
设经过一个点的期望为p[x],可以得到边的概率为
\(p(e<x,y>)p[x]/d[x] + p[y]/d[y]\)
也就是到某条特定的边的期望就是经过这个点的期望值乘上 1/该点的总边数
那么一条边被经过的期望次数就应该是两端点到这条边的期望值之和
p[i]表示经过这个点的概率,d[i]表示这个点的度数
$p[i] = \sum_((i,j)∈G)\frac{p[i]}{p[j]} $
发现会产生环,无法递推,因为对与一个点需要旁边点的期望来推,旁边点的期望需要他的期望来推
我们可以列出方程来高斯消元
\[这个点的概率\times 1-\sum所有相邻的点转移过来的概率=0
\]
注意,对于节点1我们的相减后上式结果列为1,因为比达,对于节点n,它对其他节点的贡献为0
这样我们把概率设为未知数,解方程共有n-1个点,列n-1个方程
/*
*/
#include<cstdio>
#include<algorithm>
const int maxn = 1001;
const int maxm = 1000007;
#define eps 1e-8
inline int read() {
int x = 0,f = 1;
char c = getchar();
while(c < '0' || c > '9'){if(c == '-')f = -1;c = getchar(); }
while(c <= '9' && c >= '0')x = x * 10 + c - '0',c = getchar();
return x * f;
}
int n,m;
int head[maxn],d[maxn]; double p[maxm],ans[maxn];int num = 0;
struct node {
int u,v,next;
} edge[maxm];
inline void add_edge(int u,int v) {
edge[++ num].v = v; edge[num].next = head[u];head[u] = num; }
double a[maxn][maxn];
int sign(double x) {
if(x < eps && x > -eps) return 0;
else return x < 0 ? -1 : 1;
}
void guass() {
for(int i = 1;i < n;++ i) {
int mx = i;
for(int j = i + 1;j < n;++ j)
if(sign(a[j][i] - a[mx][i]) > 0)mx = j;
if(mx != i) std::swap(a[i],a[mx]);
for(int j = i + 1;j < n;++ j) {
double tmp = a[j][i] / a[i][i];
for(int k = i;k <= n;++ k)
a[j][k] -= tmp * a[i][k];
}
}
for(int i = n - 1;i >= 1;-- i) {
for(int j = i + 1;j < n;++ j)
a[i][n] -= ans[j] * a[i][j];
ans[i] = a[i][n] / a[i][i];
}
}
int S[maxm],T[maxm];
int main() {
n = read(), m = read();
for(int u,v,i = 1;i <= m;++ i) {
u = read(),v = read();
add_edge(u,v);add_edge(v,u);
d[u] ++ ,d[v] ++;
S[i] = u,T[i] = v;
}
a[1][n] = 1.0;
for(int i = 1;i < n;++ i) a[i][i] = 1.0;
for(int i = 1;i < n;++ i)
for(int j = head[i];j;j = edge[j].next) {
if(edge[j].v != n)
a[i][edge[j].v] = (double)-1.0/d[edge[j].v];
}
guass();
for(int i = 1;i <= m;++ i)
p[i] = ans[S[i]] / (double) d[S[i]] + ans[T[i]] / (double )d[T[i]];
std::sort(p + 1,p + m + 1);
double Ans = 0;
for(int i = 1;i <= m;++ i)
Ans += p[i] * (m - i + 1);
printf("%.3lf\n",Ans);
return 0;
}