NOI2019 回家路线

传送门
参考博客

在某一条路线的终点下车后,是一个确定的时间,与前面的路线怎么走是无关的。所以可以以终止时间为线索考虑 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]+Ap[j]2+Aq[i]22Ap[j]q[i]+Bp[j]Bq[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]+Aq[i]2Bq[i])=(dp[j]Ap[j]2Bp[j]C)+(2Ap[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]+Aq[i]2Bq[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=2Ap[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]Ap[j]2Bp[j]C

也就是说,一个确定的 j j j 的若干可行转移对应着一组直线系,它们的斜率都是 2 ∗ A ∗ p [ j ] 2*A*p[j] 2Ap[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 Ap[j]2Bp[j]C为定值,若要让 d p [ j ] dp[j] dp[j]最小,就让截距最小就好了。发现满足使截距最小的点,即为斜率为 2 ∗ A ∗ p [ j ] 2*A*p[j] 2Ap[j]的一条直线,从坐标系下面的无穷远处向上平移,遇到的第一个点。那么维护一个下凸壳就好了。每次去找到斜率大于 2 ∗ A ∗ p [ j ] 2*A*p[j] 2Ap[j]的第一条直线 和 斜率小于 2 ∗ A ∗ p [ j ] 2*A*p[j] 2Ap[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 2Anow
假设当前被更新的路线为 j j j。那么我们要在 x [ j ] x[j] x[j]所维护的下凸壳中找到斜率大于 2 ∗ A ∗ n o w 2*A*now 2Anow的第一条直线 和 斜率小于 2 ∗ A ∗ n o w 2*A*now 2Anow的第一条直线 的交点来更新 d p [ j ] dp[j] dp[j]

由于时间 n o w now now是单增的,那么 2 ∗ A ∗ n o w 2*A*now 2Anow也就是单增的,于是我们可以把下凸壳中所有斜率不大于 2 ∗ A ∗ n o w 2*A*now 2Anow的线段的左下角的点弹掉。剩下的最左下角的点就是用来更新的点了。

#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);
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值