P5468 [NOI2019] 回家路线

P5468 [NOI2019] 回家路线

斜率优化入门(指我写的东西而不是这道题)

题意

n 个节点,有 m 趟列车,车 ipi 时刻至 qi 时刻从 xi 地到 yi 地。猫猫需要只坐列车从 1 节点到达 n 节点。

设猫猫在一个地方等待的时间为 t,那么代价为 At2+Bt+C,其中 A B C 为给定非负常数,总代价为所有代价总和与到达时间的和。求最小代价。

思路

fi 为走 i 趟列车前的最小代价,有转移方程:

fi=minyj=xiqjqi(fj+A(piqj)2+B(piqj)+C)

最终代价即为:

minfi+qi

对于第一个方程,不考虑其他限制,即为找到最优的 j 更新 i,有:

fi=fj+A(piqj)2+B(piqj)+Cfi=fj+Api2+Aqj22Apiqj+BpiBqj+Cfj+Aqj2Bqj=2Apiqj+fiApi2BpiC

对于已经更新过的 jfj+Aqj2Bqj 是已知的。后半部分 fiApi2BpiC 只有 fi 是未知的,也是我们要最优化的。

那么我们可以将上式看做 y=kx+b 的形式,其中

y=fj+Aqj2Bqjx=qjk=2Apib=fiApi2BpiC

(常数其实可以属于任意一个位置,上述只是举例)

那么我们可以将所有可以转移到的 j 看做一个二维平面上的点,每次更新 fi 实际是对一个斜率找出经过其中一个点得到的最优的 b。那么显然只会选到前者构成的凸包上的点。很多题查询的斜率都是单调的,而且之后在凸包后方插入新的点,于是维护凸包的数组就变成了单调队列。否则的话,就是动态维护凸包并在凸包上二分。

对于这道题,最优指最小,那么我们维护的是下凸包。

斜率优化的部分已经完了。考虑加入 yj=xiqjpi 的限制怎么做。对于第一个限制,发现不同点互不影响,那么我们对每个点开一个单调队列,对于一个列车在它的 x 位置查询就好了。对于第二个限制,我们将所有列车按照 p 排序然后枚举时间,那么对于上面更新完的列车先不插入队列,当枚举时间到 p 的时候再将其插入即可。这样就能满足所有限制,并且保证查询的斜率单调递增。

代码

好像不会炸 long long 的样子。

#include<bits/stdc++.h>
using namespace std;
inline int read(){
	int w=0,x=0;char c=getchar();
	while(!isdigit(c))w|=c=='-',c=getchar();
	while(isdigit(c))x=x*10+(c^48),c=getchar();
	return w?-x:x;
}
namespace star
{
	const int maxn=1e6+10;
	int n,T,m,A,B,C,st[maxn],ed[maxn],p[maxn],q[maxn],hd[maxn],tl[maxn];
	long long f[maxn],x[maxn],y[maxn],ans=0x3f3f3f3f3f3f3f3f;
	vector<int> V[maxn],G[maxn],que[maxn];
	inline void work(){
		n=read(),m=read(),A=read(),B=read(),C=read();
		for(int i=1;i<=m;i++) st[i]=read(),ed[i]=read(),p[i]=read(),q[i]=read(),T=max(T,q[i]),G[p[i]].push_back(i);
		for(int i=1;i<=n;i++) hd[i]=0,tl[i]=-1;
		for(int t=0;t<=T;t++){
			for(auto i:V[t]){
				int e=ed[i];
				while(hd[e]<tl[e] and 1ll*(y[que[e][tl[e]]]-y[que[e][tl[e]-1]])*(x[i]-x[que[e][tl[e]]])>=1ll*(y[i]-y[que[e][tl[e]]])*(x[que[e][tl[e]]]-x[que[e][tl[e]-1]])) --tl[e];
				if(++tl[e]==que[e].size()) que[e].push_back(i);
					else que[e][tl[e]]=i;
			}
			for(auto i:G[t]){
				int e=st[i];
				while(hd[e]<tl[e] and (y[que[e][hd[e]+1]]-y[que[e][hd[e]]])<2ll*A*p[i]*(x[que[e][hd[e]+1]]-x[que[e][hd[e]]])) ++hd[e];
				if(hd[e]>tl[e] and st[i]!=1) continue;
				int j=st[i]==1 and hd[e]>tl[e]?0:que[e][hd[e]];
				f[i]=f[j]+1ll*A*(p[i]-q[j])*(p[i]-q[j])+1ll*B*(p[i]-q[j])+C;
				x[i]=q[i],y[i]=f[i]+1ll*A*q[i]*q[i]-1ll*B*q[i];
				V[q[i]].push_back(i);
				if(ed[i]==n) ans=min(ans,f[i]+q[i]);
			}
		}
		printf("%lld\n",ans);
	}
}
signed main(){
	star::work();
	return 0;
}
posted @   Star_Cried  阅读(100)  评论(0编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现
历史上的今天:
2020-06-03 P2014选课
2020-06-03 transform和tolower
点击右上角即可分享
微信分享提示