Description
给定一张 个点 条边的无向图。当你在点 时,你可以花费 的费用向系统询问当前可以去哪个点,系统会从点 的所有出边中随机选择一条告诉你。你可以选择去,或者重新花费并询问。初始时刻你在 号点,要去 号点。求最少花费的期望。
Solution
设 表示从 号点到 号点的期望最少花费, 表示 的度数。则有转移:
这东西看着就不是很可做。 的存在使得暴力高斯消元都不可行,只能另辟蹊径。
显然,当 时,若点 对点 有贡献,则有 。
我们考虑 按照花费从小到大依次确定每个点的期望最少花费 。显然,第一次确定的是 。
从当前已经确定的点集的出边走可以找到一个可到达的点集。钦定每个点 都认为自己的 是当前可以到达的但是未确定的点中最小的 。假设这个虚假的 为 。当前暂时不能被已经确定的点到达的点的 认为是 。那么对于这个题而言,有一个如下的结论:
若 是当前集合中最小的,则 。
为什么呢?
首先,假设目前可到达但未确定的点集为 ,那么一定存在 ,满足 。因为集合 一定存在一个最小值。
也就是我们要找的东西一定是存在的。接下来我们考虑反证法。
假设目前对于 号点而言,出边中有 个点的函数值比 小,且它们的函数值之和为 (注意这些点一定是当前已确定的点)。则:
假设 ,并且 。如果我们认为 是当前集合 最小的,那么 ,且我们需要用 更新 。注意这个更新,假设更新前的 为
因为 ,所以 ,也就是变劣了。考虑 最初的方程形式的转移,如果当前我们的 取在了 处会比取在 处更劣,说明 。也就是当前不能取 。
那么通过上述的反证法,我们每次可以确定当前点集 中一个点的 ,并继续更新其余点的 。这个过程与 很像,我们参照 的实现方法即可。
复杂度 。
Code
const int N = 3e5 + 233;
using namespace std;
int n, m, num;
int deg[N], used[N], head[N], nt[N << 1], to[N << 1];
long double f[N], sum[N], cnt[N];
struct node{
long double v; int x;
node(long double v = 0, int x = 0):
v(v), x(x) {}
bool operator <(const node &b) const {
return v > b.v;
}
};
priority_queue<node>q;
void Dijkstra(){
for(int i = 1; i <= n; ++i)
f[i] = 1e100, sum[i] = cnt[i] = 0;
f[n] = 0; q.push(node(0, n));
while(!q.empty()){
int u = q.top().x; q.pop();
if(used[u]) continue;
used[u] = 1;
for(int i = head[u]; i; i = nt[i]){
int v = to[i]; if(used[v]) continue;
cnt[v] += 1; sum[v] += f[u];
f[v] = (deg[v] + sum[v]) / cnt[v];
q.push(node(f[v], v));
}
}
}
void add(int x, int y){
++num, nt[num] = head[x], head[x] = num, to[num] = y; ++deg[x];
++num, nt[num] = head[y], head[y] = num, to[num] = x; ++deg[y];
}
int main(){
fin >> n >> m;
for(int i = 1, u, v; i <= m; ++i)
fin >> u >> v, add(u, v);
Dijkstra();
printf("%.6Lf\n", f[1]);
return 0;
}