在某一条路线的终点下车后,是一个确定的时间,与前面的路线怎么走是无关的。所以可以以终止时间为线索考虑
D
P
DP
DP。
用
d
p
[
j
]
dp[j]
dp[j]表示最后走第
j
j
j号路线的最少消耗。那么对于所有的路线
i
i
i,如果路线
i
i
i满足:
y
[
i
]
=
x
[
j
]
,
q
[
i
]
≤
p
[
j
]
y[i]=x[j],q[i] \le p[j]
y[i]=x[j],q[i]≤p[j],那么可以用路线
i
i
i按照下列方式更新
d
p
[
j
]
dp[j]
dp[j]:
d
p
[
j
]
=
m
i
n
(
d
p
[
j
]
,
d
p
[
i
]
+
A
∗
(
p
[
j
]
−
q
[
i
]
)
2
+
B
∗
(
p
[
j
]
−
q
[
i
]
)
+
C
)
dp[j]=min(dp[j],dp[i]+A*(p[j]-q[i])^2+B*(p[j]-q[i])+C)
dp[j]=min(dp[j],dp[i]+A∗(p[j]−q[i])2+B∗(p[j]−q[i])+C)
考虑斜率优化。对于一个最优的转移路线
i
i
i,有如下式子:
d
p
[
j
]
=
d
p
[
i
]
+
A
∗
(
p
[
j
]
−
q
[
i
]
)
2
+
B
∗
(
p
[
j
]
−
q
[
i
]
)
+
C
dp[j]=dp[i]+A*(p[j]-q[i])^2+B*(p[j]-q[i])+C
dp[j]=dp[i]+A∗(p[j]−q[i])2+B∗(p[j]−q[i])+C
d
p
[
j
]
=
d
p
[
i
]
+
A
∗
p
[
j
]
2
+
A
∗
q
[
i
]
2
−
2
∗
A
∗
p
[
j
]
∗
q
[
i
]
+
B
∗
p
[
j
]
−
B
∗
q
[
i
]
+
C
dp[j]=dp[i]+A*p[j]^2+A*q[i]^2-2*A*p[j]*q[i]+B*p[j]-B*q[i]+C
dp[j]=dp[i]+A∗p[j]2+A∗q[i]2−2∗A∗p[j]∗q[i]+B∗p[j]−B∗q[i]+C
把只含
i
i
i的合起来,只含
j
j
j的合起来,剩下一个含有
i
i
i和
j
j
j的式子:
(
d
p
[
i
]
+
A
∗
q
[
i
]
2
−
B
∗
q
[
i
]
)
=
(
d
p
[
j
]
−
A
∗
p
[
j
]
2
−
B
∗
p
[
j
]
−
C
)
+
(
2
∗
A
∗
p
[
j
]
)
∗
(
q
[
i
]
)
(dp[i]+A*q[i]^2-B*q[i])=(dp[j]-A*p[j]^2-B*p[j]-C)+(2*A*p[j])*(q[i])
(dp[i]+A∗q[i]2−B∗q[i])=(dp[j]−A∗p[j]2−B∗p[j]−C)+(2∗A∗p[j])∗(q[i])
如何把上式看做
y
=
k
x
+
b
y=kx+b
y=kx+b的形式:
现在对于一个确定的
j
j
j,有许多可以用来更新
j
j
j的
i
i
i。对于某一个确定的路线
i
i
i,把它转化为二位平面上的点
(
q
[
i
]
,
d
p
[
i
]
+
A
∗
q
[
i
]
2
−
B
∗
q
[
i
]
)
(q[i],dp[i]+A*q[i]^2-B*q[i])
(q[i],dp[i]+A∗q[i]2−B∗q[i])。于是
j
j
j确定后会有若干个对应出来的点。直线
y
=
k
x
+
b
y=kx+b
y=kx+b的斜率斜率
k
=
2
∗
A
∗
p
[
j
]
k=2*A*p[j]
k=2∗A∗p[j],截距
b
=
d
p
[
j
]
−
A
∗
p
[
j
]
2
−
B
∗
p
[
j
]
−
C
b=dp[j]-A*p[j]^2-B*p[j]-C
b=dp[j]−A∗p[j]2−B∗p[j]−C。
也就是说,一个确定的 j j j 的若干可行转移对应着一组直线系,它们的斜率都是 2 ∗ A ∗ p [ j ] 2*A*p[j] 2∗A∗p[j],不同的直线过不同的定点(由 i i i确定出来的不同的点),它们都对应出一个截距。这时候 j j j为定值,所以截距 b b b中 − A ∗ p [ j ] 2 − B ∗ p [ j ] − C -A*p[j]^2-B*p[j]-C −A∗p[j]2−B∗p[j]−C为定值,若要让 d p [ j ] dp[j] dp[j]最小,就让截距最小就好了。发现满足使截距最小的点,即为斜率为 2 ∗ A ∗ p [ j ] 2*A*p[j] 2∗A∗p[j]的一条直线,从坐标系下面的无穷远处向上平移,遇到的第一个点。那么维护一个下凸壳就好了。每次去找到斜率大于 2 ∗ A ∗ p [ j ] 2*A*p[j] 2∗A∗p[j]的第一条直线 和 斜率小于 2 ∗ A ∗ p [ j ] 2*A*p[j] 2∗A∗p[j]的第一条直线 的交点来更新 j j j。
具体实现:按照时间
d
p
dp
dp。令当前时间为
n
o
w
now
now。
点
u
u
u记录路线对应出来的点集(按照上述方式转化)。
并且这些路线满足:
y
[
r
o
u
t
e
]
=
u
,
q
[
r
o
u
t
e
]
≤
n
o
w
y[route]=u,q[route] \le now
y[route]=u,q[route]≤now
当
n
o
w
now
now过去后,我们就找到所有
q
[
i
]
=
n
o
w
q[i]=now
q[i]=now的路线
i
i
i,把
i
i
i加进
y
[
i
]
y[i]
y[i]所维护的点集中。代码中
c
o
m
p
l
e
t
e
[
t
]
complete[t]
complete[t]就记录结束时间在
t
t
t的点。
这个集合要维护成一个下凸壳。
时间到
n
o
w
now
now的时候,要更新所有起始时间为
n
o
w
now
now的路线。那么当前的斜率也就是
2
∗
A
∗
n
o
w
2*A*now
2∗A∗now。
假设当前被更新的路线为
j
j
j。那么我们要在
x
[
j
]
x[j]
x[j]所维护的下凸壳中找到斜率大于
2
∗
A
∗
n
o
w
2*A*now
2∗A∗now的第一条直线 和 斜率小于
2
∗
A
∗
n
o
w
2*A*now
2∗A∗now的第一条直线 的交点来更新
d
p
[
j
]
dp[j]
dp[j]。
由于时间 n o w now now是单增的,那么 2 ∗ A ∗ n o w 2*A*now 2∗A∗now也就是单增的,于是我们可以把下凸壳中所有斜率不大于 2 ∗ A ∗ n o w 2*A*now 2∗A∗now的线段的左下角的点弹掉。剩下的最左下角的点就是用来更新的点了。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1e5+10,maxm=2e5+10,maxt=1e3+10;
int n,m,x[maxm],y[maxm],p[maxm],q[maxm],T=0;ll A,B,C;
ll dp[maxm],ans=9e18;int Head[maxn];
inline ll Y(int i){return dp[i]+A*q[i]*q[i]-B*q[i];}
inline ll X(int i){return q[i];}
//from route_i to route_j
inline ll cost(int i,int j){return A*(p[j]-q[i])*(p[j]-q[i])+B*(p[j]-q[i])+C;}
struct point{
ll x,y;int route;
point(int r=0){x=X(r),y=Y(r),route=r;}
};
vector<int> now_start_route[maxt];
vector<point> Q[maxn];
queue<int> complete[maxt];
//dp[i]:最后一条走i号路线的最优解。
inline int read(){
int x=0;char ch=getchar();
while(!isdigit(ch)) ch=getchar();
while(isdigit(ch)) x=x*10+ch-'0',ch=getchar();
return x;
}
//xy斜率大于k吗?
inline int comp1(const point &a,const point &b,ll k){return (b.y-a.y)>k*(b.x-a.x);}
//ab斜率大于cd吗?
inline int comp2(const point &a,const point &b,const point &c,const point &d){
return ((b.y-a.y)*(d.x-c.x))>((d.y-c.y)*(b.x-a.x));
}
inline void add_point(int route){
point now=point(route);int pos=y[route];
while(Q[pos].size()-Head[pos]>=2){
int len=Q[pos].size();
if(comp2(Q[pos][len-2],now,Q[pos][len-2],Q[pos][len-1])) break;
Q[pos].pop_back();
}Q[pos].push_back(now);
}
inline void del_point(ll k,int pos){
while(Q[pos].size()-Head[pos]>=2){
if(comp1(Q[pos][Head[pos]],Q[pos][Head[pos]+1],k)) break;
++Head[pos];
}
}
int main(){
n=read(),m=read(),A=read(),B=read(),C=read();
for(int i=1;i<=m;++i){
x[i]=read(),y[i]=read(),p[i]=read(),q[i]=read();
now_start_route[p[i]].push_back(i),T=max(T,q[i]);
}Q[1].push_back(point(0));
for(int now=0;now<=T;++now){
while(!complete[now].empty()) add_point(complete[now].front()),complete[now].pop();
int len=now_start_route[now].size();
for(int i=0;i<len;++i){
int route=now_start_route[now][i],start=x[route];
if(Q[start].size()<=Head[start]) continue;
del_point(A*p[route]*2,start);
int pre=Q[start][Head[start]].route;
dp[route]=dp[pre]+A*(p[route]-q[pre])*(p[route]-q[pre])+B*(p[route]-q[pre])+C;
complete[q[route]].push(route);
if(y[route]==n) ans=min(ans,dp[route]+q[route]);
}
}printf("%lld\n",ans);
}