[NOI2019] 回家路线 题解
[NOI2019] 回家路线 题解
第一次这么深入理解斜率优化的习题。
题意
现在有 \(n\) 个城市,城市之间有 \(m\) 条火车可以到达。
第i条火车是从第 \(x_i\) 出发并到达 \(y_i\),是在 \(p_i\) 时间出发,并在 \(q_i\) 时间到达。
火车只能够在前一辆到达后才能乘坐。
设共乘坐了k辆火车,那么他的代价是\(q_{s_{k}}+\left(A \times p_{s_{1}}^{2}+B \times p_{s_{1}}+C\right)+\sum_{j=1}^{k-1}\left(A\left(p_{s j+1}-q_{s_{j}}\right)^{2}+B\left(p_{s_{j+1}}-q_{s_{j}}\right)+C\right)\) .
求最小代价。
数据范围
原版:\(2≤n≤10^5,1≤m≤2×10^5,0\le p_i<q_i \le 10^3\)。
加强版:\(2≤n≤10^5,1\le m\le 10^6,0\le p_i<q_i\le 4\times 10^4\)。
题解
对于原版这个极小的数据范围,有优雅的暴力dp方法:
设 \(dp(i,j)\) 为在时间 \(j\) 到达 \(i\) 号点最优方案。
把所有火车按 \(p\) 或 \(q\) 排序后枚举火车进行转移即可。
复杂度 \(O(n\times q_i)\) 。勉强可过。
但是对于加强版呢?
我们来考虑使用斜率优化dp。
设 \(dp_i\) 表示乘坐第 \(i\) 辆火车后最小代价。
那么有状态转移方程:
尝试把这个式子化简一下:
移项可得:
这是斜率优化的标准式子。其中
但是这样就忽略了限制条件 \(({y_j=x_i\ ,\ q_j\le p_i})\).
- 对于前一个限制:
- 我们对于每一个节点 \(i\) 都维护一个下凸包。这样对于每一辆车 \(i\) ,它从 \(x_i\) 转移,并且计入 \(y_i\) 所在的凸包里。
- 这个过程就不能自己开数组来解决了,必须借助vector。
- 对于第二个限制:
- 首先我们考虑:最开始一定要进行一步排序,让 \(p_i\) 单调递增。因为 \(p_i\ge q_j\, , \ q_j > p_j \Rightarrow p_i>p_j\) 可以考虑所有情况。
- 既然 \(p_i\) 已经是单调递增的了,这里计算需要让凸包中所有的 \(q_j\le p_j\) 的 \(j\) 都存在于凸包中。
- 我们用一个优先队列维护已经转移过的 \(j\) ,按 \(q\) 排序 。对于一个准备计算的 \(i\) ,将所有满足第二个条件的 \(j\) 塞入所属凸包内。
整理一下思路:
- 对于一个 \(i\) ,先将优先队列内满足 \(q_j\le p_i\) 的都取出放入凸包内。
- 使用斜率优化转移求 \(dp_i\)
- 将 \(i\) 放入优先队列内等待转移给其他量。
思考
这道题提供了很好的思路。
当用一个数据结构来优化dp时,一个状态被转移完,如果还不符合用它转移其它状态的要求,就先用另一个数据结构存起来。(本题使用优先队列)。
另外,现在才发现自己在优先队列优先级设定上还有知识漏洞。
这里简单概述一下:优先队列是优先级大的在前。理解为与 sort
的 cmp
相反。
还有,斜率优化时应尽量把除化乘,避免精度丢失。(可能导致巨大的失分!)
这道题是一道很好的题,大概调了两个小时才出来。它是值得的。
代码
#include <bits/stdc++.h>
#define fo(a) freopen(a".in","r",stdin),freopen(a".out","w",stdout);
using namespace std;
const int INF = 0x3f3f3f3f, N = 4e6+5, M = 4e6+5;
typedef long long ll;
typedef unsigned long long ull;
inline ll read(){
ll ret = 0 ; char ch = ' ' , c = getchar();
while(!(c >= '0' && c <= '9')) ch = c , c = getchar();
while(c >= '0' && c <= '9')ret = (ret << 1) + (ret << 3) + c - '0' , c = getchar();
return ch == '-' ? -ret : ret;
}
#define int ll
int n,m,A,B,C;
struct Edge{int u,v,p,q,id;}e[M];
inline bool sotcmp(const Edge a,const Edge b){return a.p < b.p;}
struct pqcmp{inline bool operator () (const Edge a,const Edge b){return a.q > b.q;}};
vector<int> vc[N];
priority_queue<Edge,vector<Edge>,pqcmp> pq;
int dp[M];
inline int f(int x){return dp[x] + A*e[x].q*e[x].q - B*e[x].q;}
inline int X(int x){return e[x].q;}
inline void push(Edge a){
int v = a.v, id = a.id;
while(vc[v].size() >= 2){
int x = vc[v][vc[v].size()-2], y = vc[v][vc[v].size()-1];
if((f(x)-f(y)) * (X(x)-X(id)) >= (f(x)-f(id)) * (X(x)-X(y))) vc[v].pop_back();
else break;
}
vc[v].push_back(id);
}
int ans = 4e16;
signed main(){
n = read(), m = read(), A = read(), B = read(), C = read();
for(int i = 1 ; i <= m ; i ++){
int u = read(), v = read(), p = read(), q = read();
e[i] = (Edge){u,v,p,q,i};
}
sort(e+1,e+m+1,sotcmp);
vc[1].push_back(0);
dp[0] = 0;
while(!pq.empty()) pq.pop();
for(int i = 1 ; i <= m ; i ++){
int u = e[i].u, v = e[i].v, p = e[i].p, q = e[i].q; e[i].id = i;
while(!pq.empty() && pq.top().q <= p) push(pq.top()), pq.pop();
while(vc[u].size() >= 2){
int x = vc[u][0], y = vc[u][1];
if(1.0*(f(x)-f(y))/(X(x)-X(y)) <= 2*A*p) vc[u].erase(vc[u].begin());
else break;
}
if(vc[u].size()){
int j = vc[u][0];
dp[i] = dp[j] + A*(e[i].p-e[j].q)*(e[i].p-e[j].q) + B*(e[i].p-e[j].q) + C;
pq.push(e[i]);
if(v == n) ans = min(ans,dp[i]+q);
}
}
printf("%lld",ans);
}