[CF605E] Intergalaxy Trips
问题描述
- n 个点的有向完全图。
- \(i \to j\) 的边每天出现的概率均为 \(p_{i,j}\),若 \(i = j\),有 \(p_{i,j}\) = 1。
- 每天选择一条存在的出边走过去。
- 求最优策略下从 1 到 \(n\) 的期望天数。
- \(n \le 10^3\)。
样例输入
3
100 50 50
0 100 80
0 0 100
样例输出
2
100 30
40 100
解析
一个比较好想的策略是每次走到终点的期望天数最少的点。但这样很难确定转移,因此我们不妨倒过来转移,从n号点出发。对于期望天数第\(i\)小的节点,设其编号为\(a_i\),我们不难得到以下转移方程:
\[f_{a_i}=1\times \prod_{j=1}^{i-1}(1-p_{a_i,a_j})+\sum_{j=1}^{i}f_{a_j}\times p_{a_i,a_j}\prod_{k=1}^{j-1}(1-p_{a_i,a_k})
\]
记\(q_i=\sum_{j=1}^{i-1}(1-p_{a_i,a_j})\),稍作转化,将\(f_{a_i}\)提到等式的同一边即可。然后用类似Dijkstra的思路,每次取当前最小的节点更新即可。更新时可以同时计算\(q_i\)。
代码
#include <iostream>
#include <cstdio>
#define N 1002
using namespace std;
int n,i,j;
double f[N],p[N][N],p1[N];
bool vis[N];
int read()
{
char c=getchar();
int w=0;
while(c<'0'||c>'9') c=getchar();
while(c<='9'&&c>='0'){
w=w*10+c-'0';
c=getchar();
}
return w;
}
int main()
{
n=read();
for(i=1;i<=n;i++){
for(j=1;j<=n;j++){
int x=read();
p[i][j]=0.01*x;
}
}
for(i=1;i<=n;i++) p1[i]=1;
p1[n]=0;
for(i=1;i<=n;i++){
double minx=1e20;
int id;
for(j=1;j<=n;j++){
if(!vis[j]&&(f[j]+p1[j])/(1-p1[j])<minx) minx=(f[j]+p1[j])/(1-p1[j]),id=j;
}
vis[id]=1;f[id]=minx;
if(id==1){
printf("%.10lf\n",minx);
break;
}
for(j=1;j<=n;j++){
if(!vis[j]) f[j]+=p[j][id]*p1[j]*(f[id]+1),p1[j]*=(1-p[j][id]);
}
}
return 0;
}