【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;
}
posted @ 2021-01-13 07:34  stoorz  阅读(72)  评论(0编辑  收藏  举报