【斜率优化】【P5468】 [NOI2019]回家路线

Description

给定 n 点,这 n 个点由 m 班列车穿插连结。对于第 i 班列车,会在 pi 时刻从 xi 站点出发开向 yi 站点,到站时间为 qi。现在从 1 号节点出发,经过多次换乘到达节点 n。一次换乘是指对于两班列车,假设分别为 u 号与 v 号列车,若 yu=xv 并且 qupv,那么小猫可以乘坐完 u 号列车后在 yu 号站点等待 pvqu 个时刻,并在时刻 pv 乘坐 v 号列车。要求最小化路程中的烦躁值。烦躁值的计算方法是:给定参数 A,B,C,对于每次等待,假设等待了 t 个时刻,那么烦躁值增加 At2+Bt+C。另外如果在时刻 T 到达节点 n,则狂躁值再增加 T

Limitations

1n, m2×105

0A10,  0B, C106

1piqi103

保证 1 可以到达 n

Solution

好像正解是以时间为状态DP来着……然而拿到这个题的第一反应是DP每条边到 n 的贡献。

考虑设 fi 是第 i 条边开始出发,到达 n 号节点对烦躁值的最小贡献。于是有转移方程:

fi=minpjqixj=yi{fj+A(pjqi)2+B(pjqi)+C+pjqi}+qipi

fi=minpjqixj=yi{fj+Apj22Apjqi+Aqi2+BpjBqi+C+pjqi}+qipi

将与 j 无关的项提出大括号,得到

fi=minpjqixj=yi{fj+Apj22Apjqi+Bpj+pj}+qipi+Aqi2qiBqi+C

整理得

fi=minpjqixj=yi{fj+Apj22Apjqi+(B+1)pj}+Aqi2Bqi+Cpi

gi=Aqi2Bqipi+Chi=Api2+(B+1)pj

上式即为

fi=fj+hj2Apjqi+gi

移相得到

fj+hj=2Aqiqj+figi

注意到 gi 是一个与 i 有关的常数,那么最小化 fi 只需要最小化 fi+gi

如果将 fj+hj 看作纵坐标, qj 看作横坐标,上述方程可以看成一条斜率为常数 2Aqi 的直线,在所有满足条件的 j 中选择一个点,使得直线过这个点,最小化直线在 y 轴上的截距 fi+gi

那么所有满足条件的点显然在一个下突壳上,证明上可以考虑如果一个点不在下突壳上那么能找到一个更优的 j

然后考虑如果 i 会从 j 转移过来,那么一定有 qipj,又因为 pj<qj,因此 qi<qj,于是按照 q 的不升序进行排序即可。

在转移时,只需要在每个点维护一个凸壳表示所有可能被选择的点,然后再维护一个 set 记录该点上已经被计算但是没有插入凸壳的点。set 内部按照 p 的不升序排序,每次要转移一条边 i 的时候先将终点中 p 不小于 qi 的边插入凸壳,由于转移是按照 qi 的顺序进行的,已经被插入凸壳的点一定是合法可以转移的。

另外注意到 qi 是单调不升的,于是斜率 2Aqi 也是单调升的,再考虑到插入的横轴 pj 也是单调不升的,因此可以使用单调队列维护每个点的凸壳即可。

需要注意的一点细节是凸壳的横轴是从大到小插入的,在维护的时候不要把大于号小于号写反。

一共进行了 m 次转移,每次转移复杂度 O(1),因此 DP 过程的时间复杂度是 O(m),但是由于进行了排序,且每条边插入在 set 中 1 次,所以整个算法的时间复杂度 O(mlogm)

Code

#include <cstdio>
#include <set>
#include <queue>
#include <algorithm>

const int maxn = 200005;
const ll INF = 1ll << 50;

int n, m;
ll A, B, C, ans;
ll frog[maxn], g[maxn], h[maxn];

struct M {
  int x, y, p, q;

  inline bool operator<(const M &_others) const {
    return this->q > _others.q;
  }
};
M MU[maxn];

struct Cmp {
  inline bool operator() (const int &_a, const int &_b) {
    if (MU[_a].p != MU[_b].p) {
      return MU[_a].p > MU[_b].p;
    } else {
      return _a < _b;
    }
  }
};

std::deque<int>Q[maxn];
std::set<int, Cmp>s[maxn];

int query(const int x);
void free(const int x, const int y);
void insert(const int x, const int y);

signed main() {
  freopen("route.in", "r", stdin);
  freopen("route.out", "w", stdout);
  qr(n); qr(m); qr(A); qr(B); qr(C);
  for (int i = 1; i <= m; ++i) {
    qr(MU[i].x); qr(MU[i].y); qr(MU[i].p); qr(MU[i].q);
  }
  frog[m + 1] = 1ll << 50;
  std::sort(MU + 1, MU + 1 + m);
  for (int i = 1; i <= m; ++i) {
    g[i] = A * MU[i].q * MU[i].q - B * MU[i].q -  MU[i].p + C;
    h[i] = A * MU[i].p * MU[i].p + (B + 1) * MU[i].p;
  }
  for (int i = 1; i <= m; ++i) if (MU[i].y == n) {
    frog[i] = MU[i].q - MU[i].p;
    s[MU[i].x].insert(i);
  } else {
    int y = MU[i].y;
    free(y, MU[i].q);
    int j = query(i);
    if (!j) {
      frog[i] = INF;
      continue;
    }
    frog[i] = frog[j] + g[i] + h[j] - ((A * MU[i].q * MU[j].p) << 1);
    s[MU[i].x].insert(i);
  }
  ans = 1ll << 50;
  for (int i = 1; i <= m; ++i) if (MU[i].x == 1) {
    ans = std::min(frog[i] + A * MU[i].p * MU[i].p + B * MU[i].p + C + MU[i].p, ans);
  }
  qw(ans, '\n', true);
  return 0;
}

void free(const int x, const int y) {
  while (!s[x].empty()) {
    auto u = *s[x].begin();
    if (MU[u].p >= y) {
      insert(x, u);
      s[x].erase(u);
    } else {
      break;
    }
  }
}

inline std::pair<ll, ll> calc(const int x) {
  return std::make_pair(frog[x] + h[x], 1ll * MU[x].p);
}

inline bool judge(const int x, const int y, const int z) {
  auto i = calc(x), j = calc(y), k = calc(z);
  return (k.first <= j.first) && ((i.first - k.first) * (i.second - j.second) < (i.first - j.first) * (i.second - k.second));
}

void insert(const int x, const int y) {
  while (Q[x].size() > 1) {
    int i = Q[x].back(); Q[x].pop_back(); int j = Q[x].back();
    if (judge(j, i, y)) {
      Q[x].push_back(i); break;
    }
  }
  Q[x].push_back(y);
}

int query(const int p) {
  int x = MU[p].y;
  while (Q[x].size() > 1) {
    int i = Q[x].front(); Q[x].pop_front(); int j = Q[x].front();
    auto s = calc(i), t = calc(j); ll k = ((A * MU[p].q) << 1);
    if ((k * (s.second - t.second)) > (s.first - t.first)) {
      Q[x].push_front(i);
      break;
    }
  }
  return Q[x].size () ? Q[x].front() : 0;
}
posted @   一扶苏一  阅读(312)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)
历史上的今天:
2018-07-21 【DP】【P1941】【NOIP2014D1T3】飞扬的小鸟
点击右上角即可分享
微信分享提示