[NOI2019] 回家路线 题解
第一次这么深入理解斜率优化的习题。
题意
现在有 (n) 个城市,城市之间有 (m) 条火车可以到达。
第i条火车是从第 (x_i) 出发并到达 (y_i),是在 (p_i) 时间出发,并在 (q_i) 时间到达。
火车只能够在前一辆到达后才能乘坐。
设共乘坐了k辆火车,那么他的代价是(q_{s_{k}}+left(A imes p_{s_{1}}^{2}+B imes p_{s_{1}}+C ight)+sum_{j=1}^{k-1}left(Aleft(p_{s j+1}-q_{s_{j}} ight)^{2}+Bleft(p_{s_{j+1}}-q_{s_{j}} ight)+C ight)) .
求最小代价。
数据范围
原版:(2≤n≤10^5,1≤m≤2×10^5,0le p_i<q_i le 10^3)。
加强版:(2≤n≤10^5,1le mle 10^6,0le p_i<q_ile 4 imes 10^4)。
题解
对于原版这个极小的数据范围,有优雅的暴力dp方法:
设 (dp(i,j)) 为在时间 (j) 到达 (i) 号点最优方案。
把所有火车按 (p) 或 (q) 排序后枚举火车进行转移即可。
复杂度 (O(n imes q_i)) 。勉强可过。
但是对于加强版呢?
我们来考虑使用斜率优化dp。
设 (dp_i) 表示乘坐第 (i) 辆火车后最小代价。
那么有状态转移方程:
尝试把这个式子化简一下:
移项可得:
这是斜率优化的标准式子。其中
但是这样就忽略了限制条件 (({y_j=x_i , q_jle p_i})).
- 对于前一个限制:
- 我们对于每一个节点 (i) 都维护一个下凸包。这样对于每一辆车 (i) ,它从 (x_i) 转移,并且计入 (y_i) 所在的凸包里。
- 这个过程就不能自己开数组来解决了,必须借助vector。
- 对于第二个限制:
- 首先我们考虑:最开始一定要进行一步排序,让 (p_i) 单调递增。因为 (p_ige q_j\, , q_j > p_j Rightarrow p_i>p_j) 可以考虑所有情况。
- 既然 (p_i) 已经是单调递增的了,这里计算需要让凸包中所有的 (q_jle p_j) 的 (j) 都存在于凸包中。
- 我们用一个优先队列维护已经转移过的 (j) ,按 (q) 排序 。对于一个准备计算的 (i) ,将所有满足第二个条件的 (j) 塞入所属凸包内。
整理一下思路:
- 对于一个 (i) ,先将优先队列内满足 (q_jle 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);
}