CF605E Intergalaxy Trips

CF605E Intergalaxy Trips

考虑你是不知道后来的边的出现情况的,所以可以这样做:每天你都选择一些点进行观察,知道某天往这些点里面的某条边可用了,你就往这条边走。这样贪心总是对的。

我们定义一个点的权值就是这个点到$n$的期望距离。同时它就是我们要算的答案。

但是注意到一个性质,我们总是从期望较大的点走向期望较小的点(显然的)。

所以我们可以类似反过来的 dijkstra 的更新,维护当前权值的点,然后这个点当前的值就必然是最终这个点的答案。所以我们可以拿它去更新到达它的点。

加入我们当前打算使用点$u$当前我们要考虑所有可以到达$u$的点,这些点已经被某些其他点更新过了。但是我们知道更新这些点的点肯定比$u$小。我们考虑当前枚举一个点$v$,然后尝试用$u$去更新$v$。注意$v$已经被一些点(设为$a_{1\dots n}$) 更新过了,并且$a_{1\dots n}$的权值都小于$u$。

这个时候我们考虑某一天,要么$v$到$a_{1\dots n}$至少一个点有边,这种情况下无论$v$到$u$是否有边都会走到$a_{1\dots n}$。所以真正能够通过$v$走到$u$的情况是某一天$v$到$u$有边并且到其他的点没边。

注意任意选择一个前缀时,等在这个地方的概率不同,是需要计算进去的。

注意$n$只有$10^3$,可以直接跑$O(n^2)$的 dijkstra 跑过去。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include "iostream"
#include "algorithm"
#include "cstring"
#include "cstdio"
#include "queue"
using namespace std;
#define MAXN 1006
int n;
double p[MAXN][MAXN];
double dp[MAXN] , tmp[MAXN] , f[MAXN];
int vis[MAXN];
int main() {
cin >> n;
for( int i = 1 ; i <= n ; ++ i )
for( int j = 1 , q ; j <= n ; ++ j ) scanf("%d",&q) , p[i][j] = 1.0 * q / 100;
for( int i = 1 ; i <= n ; ++ i ) dp[i] = 3e18 , tmp[i] = 1;
dp[n] = 0;
for( int i = 1 , u = 0 ; i <= n ; ++ i ) {
double mn = 3e18;
for( int j = 1 ; j <= n ; ++ j ) if( !vis[j] && dp[j] < mn ) mn = dp[j] , u = j;
vis[u] = 1;
for( int v = 1 ; v <= n ; ++ v ) if( !vis[v] && p[v][u] > 1e-8 ) {
double ls = 1 - tmp[v]; tmp[v] *= 1.0 - p[v][u];
double t = ls / ( 1 - tmp[v] ); // 在这个点的情况中,其他边出现至少一条的概率 / 当前边加入后至少出现一条的概率
double r1 = t * f[v] , r2 = ( 1 - t ) * dp[u]; // 两种情况,要么按照以前的边有出现,要么只有新加入的边出现
f[v] = r1 + r2;
dp[v] = min( dp[v] , f[v] + 1 / ( 1 - tmp[v] )); // 考虑等着
}
}
printf("%.7lf",dp[1]);
}
文章作者: yijan
文章链接: https://yijan.co/old54/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Yijan's Blog