【CF605E】Intergalaxy Trips
题目
题目链接:https://codeforces.com/problemset/problem/235/C
- \(n\) 个点的有向完全图。
- \(i \to j\) 的边每天出现的概率均为 \(p_{i,j}\),若 \(i = j\),有 \(p_{i,j} = 1\)。
- 每天选择一条存在的出边走过去。
- 求最优策略下从 \(1\) 到 \(n\) 的期望天数。
- \(n \le 10^3\)。
思路
设 \(f_i\) 表示从点 \(i\) 到点 \(n\) 的期望步数。
显然当我们最终把 \(f\) 按从小到大排序之后,一定是排在后面的转移到排在前面的。
所以我们需要确定一个顺序保证转移只会从最终 \(f\) 较小的转移到 \(f\) 较大的。
不难发现在某一个状态下,肯定选择没有选择过且 \(f\) 最小的进行转移就可以满足上述条件。所以我们用类似 dij 的转移方式。
那么有
\[f_i=\sum^{}_{f_j<f_i}f_j\times p_{i,j}\times \sum^{}_{f_k<f_j}(1-p_{i-k})
\]
因为如果我们考虑走到 \(j\),当且仅当比它期望步数更小的我们都没法走,且我们能走向 \(j\)。
记 \(g_i\) 为截止当前转移最后一个 \(\sum\) 内的值,就可以 \(O(1)\) 转移了。
时间复杂度 \(O(n^2)\)。
代码
#include <bits/stdc++.h>
using namespace std;
const int N=1010;
int n;
double f[N],g[N],p[N][N];
bool vis[N];
int main()
{
scanf("%d",&n);
for (int i=1;i<=n;i++)
{
f[i]=g[i]=1;
for (int j=1;j<=n;j++)
{
scanf("%lf",&p[i][j]);
p[i][j]/=100.0;
}
}
f[n]=g[n]=0;
for (int i=1;i<=n;i++)
{
int x=0;
for (int j=1;j<=n;j++)
if (!vis[j] && g[j]<1 && (!x || f[j]/(1-g[j])<f[x]/(1-g[x]))) x=j;
vis[x]=1; f[x]/=(1-g[x]);
for (int j=1;j<=n;j++)
if (!vis[j])
{
f[j]+=f[x]*p[j][x]*g[j];
g[j]*=(1-p[j][x]);
}
}
printf("%.10lf",f[1]);
return 0;
}