题目:BZOJ3143.
题目大意:给定一张简单无向图,现在小Z从点
1
1
1出发,每次等概率随机游走到一个有边相连的点并且获得等于该边编号的分数.现在让你给
m
m
m条边编号,使得第一次走到点
n
n
n时分数的期望值最小.
1
≤
n
≤
500
1\leq n\leq 500
1≤n≤500.
首先很显然边的期望经过次数越大,它的编号就应该越小,问题变为求每条边的期望经过次数.
由于算出边的期望经过次数比较困难,我们可以转化为经过点的期望次数,然后通过点的期望经过次数除掉点的度来算出边的期望经过次数.设边
(
x
,
y
)
(x,y)
(x,y)的期望经过次数为
F
x
,
y
F_{x,y}
Fx,y,点
x
x
x的期望经过次数为
f
x
f_x
fx,点
x
x
x的度为
d
e
g
[
x
]
deg[x]
deg[x],那么有:
F
x
,
y
=
f
x
d
e
g
[
x
]
+
f
y
d
e
g
[
y
]
F_{x,y}=\frac{f_x}{deg[x]}+\frac{f_y}{deg[y]}
Fx,y=deg[x]fx+deg[y]fy
考虑DP,沿用上面
f
f
f的定义,很容易列出方程:
f
x
=
∑
(
y
,
x
)
∈
E
f
y
d
e
g
[
y
]
f_x=\sum_{(y,x)\in E}\frac{f_y}{deg[y]}
fx=(y,x)∈E∑deg[y]fy
考虑这个DP有后效性,但是这里 n n n的点的方程合起来会成为一个线性方程组,所以高斯消元即可.时间复杂度 O ( n 3 ) O(n^3) O(n3).
这里需要注意两点,第一个是与 n n n有关点的贡献都不用算,最后直接 f n = 1 f_n=1 fn=1即可;另一个是 f 1 f_1 f1在原方程的基础上还要 + 1 +1 +1.
为什么别人都用邻接表存边啊…
代码如下:
#include<bits/stdc++.h>
using namespace std;
#define Abigail inline void
typedef long long LL;
const int N=500;
const double eps=0.0000001;
int n,m,deg[N+9],x[N*N+9],y[N*N+9];
double f[N*N+9],ans; //数组开成f[N+9]WA了N发...
struct matrix{
int n,m;
double v[N+9][N+9];
}a;
void Gauss(matrix &a){
int now=1,r,n=a.n;
double t;
for (int i=1;i<=n;++i){
for (r=now;r<=n;++r)
if (fabs(a.v[r][i])>eps) break;
if (r>n) continue;
if (r^now) swap(a.v[r],a.v[now]);
t=a.v[now][i];
for (int j=1;j<=n+1;++j) a.v[now][j]/=t;
for (int j=1;j<=n;++j)
if (j^now){
t=a.v[j][i];
for (int k=1;k<=n+1;++k)
a.v[j][k]-=t*a.v[now][k];
}
++now;
}
}
Abigail into(){
scanf("%d%d",&n,&m);
for (int i=1;i<=m;++i){
scanf("%d%d",&x[i],&y[i]);
++deg[x[i]];++deg[y[i]];
}
}
Abigail work(){
a.n=n-1;
for (int i=1;i<=m;++i){
if (x[i]==n||y[i]==n) continue;
a.v[x[i]][y[i]]=1.0/deg[y[i]];
a.v[y[i]][x[i]]=1.0/deg[x[i]];
}
for (int i=1;i<n;++i)
a.v[i][i]=-1.0;
a.v[1][n]=-1.0;
Gauss(a);
for (int i=1;i<=m;++i){
if (x[i]^n) f[i]+=a.v[x[i]][n]/deg[x[i]];
if (y[i]^n) f[i]+=a.v[y[i]][n]/deg[y[i]];
}
sort(f+1,f+1+m);
for (int i=1;i<=m;++i)
ans+=f[i]*(m-i+1);
}
Abigail outo(){
printf("%.3lf\n",ans);
}
int main(){
into();
work();
outo();
return 0;
}