题目链接:https://atcoder.jp/contests/abc204/tasks/abc204_e
给定一张无向图,一个人时刻 t t t从一个点走到另一个点需要使用 C i + ⌊ D i t + 1 ⌋ C_i + \lfloor \frac{D_i}{t + 1} \rfloor Ci+⌊t+1Di⌋的时间。起点是1号点,终点是n号点,起始时刻是0,可以在某一点等待任意秒后再出发。求从起点到终点的最短时间。
设到达 u u u点的时刻为 t t t,下一个点是 v v v,设在u点等待了 x x x秒,那么到下一个点的时刻
f ( x ) = x + C i + ⌊ D i t + 1 + x ⌋ f(x) = x + C_i + \lfloor \frac{D_i}{t + 1 + x} \rfloor f(x)=x+Ci+⌊t+1+xDi⌋, 其中 C i C_i Ci、 D i D_i Di与 t t t是常数。看到这个函数, 很容易陷入一个误区,那就是认为 f ( x ) f(x) f(x)是一个凸函数(对勾函数),然后三分求解。但实际上由于取整函数的原因, f ( x ) f(x) f(x)不连续,真实的 f ( x ) f(x) f(x)的图像如下:
真实的函数由若干斜率为1的直线构成,所以正确的做法应该是取 x = D i − t − 1 x=\sqrt{D_i}-t-1 x=Di−t−1(均值不等式),然后在x的邻域搜索最小的 t t t,然后跑一边堆优化迪杰斯特拉即可。
代码如下:
#include
using namespace std;
const int N = 2e5 + 5;
#define int long long
int e[N], h[N], idx, c[N], d[N], dist[N], ne[N];
bool st[N];
using pii = pair<int, int>;
void add(int a, int b, int C, int D){
e[idx] = b, c[idx] = C, d[idx] = D, ne[idx] = h[a], h[a] = idx++;
}
void dij(){
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
priority_queue<pii, vector<pii>, greater<pii> >q;
q.push({0, 1});
while(q.size()){
int t = q.top().second;
q.pop();
if(st[t]) continue;
st[t] = 1;
for(int i = h[t];~i;i = ne[i]){
int j = e[i];
int x = c[i] + d[i] / (dist[t] + 1);
for (int _ = max((int)sqrt(d[i]) - 3 - dist[t], 0ll); _ <= sqrt(d[i]) + 3 - dist[t]; ++ _){
if (_ < dist[t]) continue;
x = min(_ + c[i] + d[i] / (_ + dist[t] + 1), x);
}
if(dist[j] > dist[t] + x){
dist[j] = x + dist[t];
q.push({dist[j], j});
}
}
}
}
signed main(){
memset(h, -1, sizeof h);
int n, m;
scanf("%lld%lld", &n, &m);
for(int i = 0;i < m; i++){
int a, b, C, D;
scanf("%lld%lld%lld%lld", &a, &b, &C, &D);
add(a, b, C, D);
add(b, a, C, D);
}
dij();
cout << (dist[n] == 0x3f3f3f3f3f3f3f3f ? -1 : dist[n]);
return 0;
}X