CF553E Kyoya and Train 题解
题意
- 给定一张 个点 条边的无重边无自环的有向图,你要从 号点到 号点去。
- 如果你在 时刻之后到达 号点,你要交 元的罚款。
- 每条边从 到 ,走过它需要花费 元,多次走过同一条边需要多次花费。
- 走过每条边所需的时间是随机的,对于 , 表示走过第 条边需要时间 的概率。因此如果多次走过同一条边,所需的时间也可能不同。
- 你希望花费尽可能少的钱(花费与罚款之和)到达 号点,因此每到达一个点,你可能会更改原有的计划。
- 求在最优决策下,你期望花费的钱数。
- ,,,,,答案精度误差 。
题解
首先给出暴力 dp 的形式:
设 表示到点 且已经过去 时间后,在最优决策下的最小花费,则我们的目标即为 。
转移如下:
其中 表示 的最小花费。
考虑这个 dp 的正确性,由于转移时 严格变小,因此这个 dp 实际上是个 DAG。
直接做的话,总转移数是 的,每次转移需要 ,总时间复杂度 ,显然不够优秀。
注意到 是一个卷积,考虑分治 FFT 优化,时间复杂度降为 。
另外,在算 的时候可以 Floyd,时间复杂度 。
接下来我们具体来实现一下分治 FFT,首先处理出 和 ,为了方便,可以先去掉 的所有出边。
再设 表示在时刻 选择走第 条边以及之后的最小花费,则有 。
对于 ,我们先计算 并求出 ,再考虑 对 的贡献,最后计算 。
考虑对于 ,设 , 对 的贡献为: 设 ,则转化为: FFT 即可。
需要注意的是,由于我们 CDQ 分治的时候只分治 ,因此 的贡献需要先额外计算,这部分并不会影响时间复杂度。
当然,还可以分治 ,但是在第一层分治时跳过计算 即可。
代码
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;
}
评论
暂无已公开评论。