← 全部文章

CF553E Kyoya and Train 题解

本文目录(3 节)
  1. 题意
  2. 题解
  3. 代码

CF553E Kyoya and Train

题意

  • 给定一张 nn 个点 mm 条边的无重边无自环的有向图,你要从 11 号点到 nn 号点去。
  • 如果你在 tt 时刻之后到达 nn 号点,你要交 xx 元的罚款。
  • 每条边从 aia_i 到 bib_i,走过它需要花费 cic_i 元,多次走过同一条边需要多次花费。
  • 走过每条边所需的时间是随机的,对于 k∈[1,t]k \in [1,t],pi,k105\frac{p_{i,k}}{10^5} 表示走过第 ii 条边需要时间 kk 的概率。因此如果多次走过同一条边,所需的时间也可能不同。
  • 你希望花费尽可能少的钱(花费与罚款之和)到达 nn 号点,因此每到达一个点,你可能会更改原有的计划。
  • 求在最优决策下,你期望花费的钱数。
  • n≤50n \le 50,m≤100m \le 100,t≤2×104t \le 2 \times 10^4,x,ci≤106x,c_i \le 10^6,∑k=1tpi,k=105\sum_{k=1}^t p_{i,k} = 10^5,答案精度误差 ≤10−6\le 10^{-6}。

题解

首先给出暴力 dp 的形式:

设 fi,jf_{i,j} 表示到点 ii 且已经过去 jj 时间后,在最优决策下的最小花费,则我们的目标即为 f1,0f_{1,0}。

转移如下: fi,j={0i=n,j≤txi=n,j>tdist⁡(i,n)+xi≠n,j≥tmin⁡ak=i(∑l=1tpk,lfbk,j+l+ck)i≠n,j<t f_{i,j} = \begin{cases}0 & i = n, j \le t \\\\ x & i = n, j > t \\\\ \operatorname{dist}(i, n) + x & i \ne n, j \ge t \\\\ \min_{a_k = i} \left(\sum_{l=1}^t p_{k,l}f_{b_k,j+l} + c_k\right) & i \ne n, j < t \\\\ \end{cases}

其中 dist⁡(x,y)\operatorname{dist}(x,y) 表示 x→yx \to y 的最小花费。

考虑这个 dp 的正确性,由于转移时 jj 严格变小,因此这个 dp 实际上是个 DAG。

直接做的话,总转移数是 O(mt)\mathcal O(mt) 的,每次转移需要 O(t)\mathcal O(t),总时间复杂度 O(mt2)\mathcal O(mt^2),显然不够优秀。

注意到 ∑l=1tpk,lfbk,j+l\sum_{l=1}^t p_{k,l}f_{b_k,j+l} 是一个卷积,考虑分治 FFT 优化,时间复杂度降为 O(mtlog⁡2t)\mathcal O(mt\log^2 t)。

另外,在算 dist⁡(x,y)\operatorname{dist}(x,y) 的时候可以 Floyd,时间复杂度 O(n3)\mathcal O(n^3)。

接下来我们具体来实现一下分治 FFT,首先处理出 f∗,t…2t−1f_{*,t\dots 2t-1} 和 fn,∗f_{n,*},为了方便,可以先去掉 nn 的所有出边。

再设 gi,jg_{i,j} 表示在时刻 jj 选择走第 ii 条边以及之后的最小花费,则有 fai,j←gi,jf_{a_i,j} \leftarrow g_{i,j}。

对于 g∗,l…rg_{*,l\dots r},我们先计算 g∗,mid+1…rg_{*,mid+1\dots r} 并求出 f∗,mid+1…rf_{*,mid+1\dots r},再考虑 f∗,mid+1…rf_{*,mid+1\dots r} 对 g∗,l…midg_{*,l\dots mid} 的贡献,最后计算 g∗,l…midg_{*,l\dots mid}。

考虑对于 x∈[l,mid],i∈[1,m]x \in [l,mid],i \in [1,m],设 y=biy = b_i,fy,mid+1…rf_{y,mid+1\dots r} 对 gi,xg_{i,x} 的贡献为: ∑j=mid+1rpi,j−xfy,j \sum_{j=mid+1}^{r} p_{i,j-x}f_{y,j} 设 aj=pi,j+1,bj=fy,r−ja_j = p_{i,j+1},b_j=f_{y,r-j},则转化为: ∑j=0r−mid−1aj+mid−xbr−mid−1−j \sum_{j=0}^{r-mid-1} a_{j+mid-x}b_{r-mid-1-j} FFT 即可。

需要注意的是,由于我们 CDQ 分治的时候只分治 0∼t−10\sim t - 1,因此 t∼2t−1t \sim 2t-1 的贡献需要先额外计算,这部分并不会影响时间复杂度。

当然,还可以分治 0∼2t−10\sim 2t-1,但是在第一层分治时跳过计算 fmid+1…rf_{mid+1 \dots r} 即可。

代码

namespace FFT {
	const int N = 1 << 21 | 1;
	const double PI = acos(-1);
	struct I {
		double x, y;
		inline I() {}
		inline I(double x, double y) : x(x), y(y) {}
		inline I &operator = (int o) { return x = o, y = 0, *this; }
		inline I &operator = (double o) { return x = o, y = 0, *this; }
		inline I operator + (const I o) const { return I(x + o.x, y + o.y); }
		inline I operator - (const I o) const { return I(x - o.x, y - o.y); }
		inline I operator * (const I o) const {
			return I(x * o.x - y * o.y, x * o.y + y * o.x);
		}
	} a[N], b[N];
	inline void rd(I &x) { int o; ::rd(o), x = o; }
	inline void print(I x, char k = '\n') { ::print((int)(x.x + 0.5), k); }
	int n, m, k, l, r[N];
	inline void fft(I *a, int n, int x) {
		for (int i = 0; i < n; i++) if (i < r[i]) swap(a[i], a[r[i]]);
		for (int o = 2, k = 1; o <= n; o <<= 1, k <<= 1) {
			I W = I(cos(PI / k), x * sin(PI / k));
			for (int i = 0; i < n; i += o) {
				I w = I(1, 0);
				for (int j = 0; j < k; j++, w = w * W) {
					I x = a[i+j], y = w * a[i+j+k];
					a[i+j] = x + y, a[i+j+k] = x - y;
				}
			}
		}
	}
	inline void solve() {
		k = 1, l = 0;
		while (k <= n + m) k <<= 1, ++l;
		for (int i = 0; i < k; i++)
			r[i] = (r[i>>1] >> 1) | ((i & 1) << (l - 1));
		for (int i = n + 1; i < k; i++) a[i] = 0;
		for (int i = m + 1; i < k; i++) b[i] = 0;
		fft(a, k, 1), fft(b, k, 1);
		for (int i = 0; i < k; i++) a[i] = a[i] * b[i];
		fft(a, k, -1);
		for (int i = 0; i <= n + m; i++) a[i].x /= k;
	}
}

const int N = 53, M = 107, T = 2e4 + 7;
const double inf = 1e18;
int n, m, t, x, a[M], b[M];
double d[N][N], c[M], p[M][T*2], f[N][T*2], g[M][T];

void cdq(int l, int r) {
	if (l == t) return;
	if (l == r) {
		for (int i = 1; i < n; i++) f[i][l] = inf;
		for (int i = 1; i <= m; i++)
			if (a[i] != n) f[a[i]][l] = min(f[a[i]][l], g[i][l] + c[i]);
		return;
	}
	int mid = (l + r) >> 1;
	cdq(mid + 1, r);
	FFT::n = r - l - 1, FFT::m = r - mid - 1;
	for (int i = 1; i <= m; i++)
		if (a[i] != n) {
			for (int j = 0; j <= FFT::n; j++) FFT::a[j] = p[i][j+1];
			for (int j = 0; j <= FFT::m; j++) FFT::b[j] = f[b[i]][r-j];
			FFT::solve();
			for (int j = l; j <= mid; j++) g[i][j] += FFT::a[r-j-1].x;
		}
	cdq(l, mid);
}

int main() {
	rd(n), rd(m), rd(t), rd(x);
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= n; j++)
			if (i != j) d[i][j] = inf;
	for (int i = 1, x; i <= m; i++) {
		rd(a[i]), rd(b[i]), rd(x), c[i] = d[a[i]][b[i]] = x;
		for (int j = 1; j <= t; j++) rd(x), p[i][j] = x / 1e5;
	}
	for (int k = 1; k <= n; k++)
		for (int i = 1; i <= n; i++)
			for (int j = 1; j <= n; j++)
				d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
	for (int i = 0; i < 2 * t; i++) f[n][i] = i <= t ? 0 : x;
	for (int i = 1; i < n; i++)
		for (int j = t; j < 2 * t; j++)
			f[i][j] = d[i][n] + x;
	cdq(0, 2 * t - 1);
	printf("%.10f\n", f[1][0]);
	return 0;
}

\textstyle

标签:CDQ 分治、DP、FFT、Floyd

评论

    填写昵称即可留言。评论经站主审核后公开,请勿提交私人信息。

    正在连接评论服务…