[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\) 辆火车后最小代价。

那么有状态转移方程:

\[dp_i = \min\limits_{y_j=x_i\ ,\ q_j\le p_i}\{dp_j+A\times(p_i-q_j)^2 + B\times(p_i-q_j)+C \} . \]

尝试把这个式子化简一下:

\[dp_i = dp_j+A\times p_i^2-2A\times p_i\cdot q_j+A\times q_j^2 + B\times p_i-B\times q_j+C .\qquad ({y_j=x_i\ ,\ q_j\le p_i}) \]

移项可得:

\[dp_j+A\cdot q_j^2-B\cdot q_j = (2A\cdot p_i\cdot q_j)+(dp_i-A\cdot p_i^2-B\cdot p_i-C). \]

这是斜率优化的标准式子。其中

\[y=dp_j+A\cdot q_j^2-B\cdot q_j,\\ kx = (2A\cdot p_i\cdot q_j),\\ b = (dp_i-A\cdot p_i^2-B\cdot p_i-C) \]

但是这样就忽略了限制条件 \(({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时,一个状态被转移完,如果还不符合用它转移其它状态的要求,就先用另一个数据结构存起来。(本题使用优先队列)

另外,现在才发现自己在优先队列优先级设定上还有知识漏洞。

这里简单概述一下:优先队列是优先级大的在前。理解为与 sortcmp 相反。

还有,斜率优化时应尽量把除化乘,避免精度丢失。(可能导致巨大的失分!)

这道题是一道很好的题,大概调了两个小时才出来。它是值得的。

代码

#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);
}
posted @ 2021-10-26 10:37  Last-Order  阅读(128)  评论(0编辑  收藏  举报