问题描述
- n 个点的有向完全图。
- (i o 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 imes prod_{j=1}^{i-1}(1-p_{a_i,a_j})+sum_{j=1}^{i}f_{a_j} imes 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
",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;
}