[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;
}

posted @ 2020-08-06 00:12  CJlzf  阅读(127)  评论(0编辑  收藏  举报