把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【洛谷6302】[NOI2019] 回家路线 加强版(斜率优化)

点此看题面

  • 有一张\(n\)个点的图和三个参数\(A,B,C\),你需要从\(1\)号点走到\(n\)号点。
  • \(m\)条有向边,每条边有一个出发时间和到达时间,只有在出发时间之前到达出发点才能选择走这条边。
  • 走两条边的间隔可能存在等待时间,假设一次等待时间为\(x\),则需要花费\(Ax^2+Bx+C\)的代价。
  • 假设你在\(t\)时刻到达\(n\)号点,还需要额外花费\(t\)的代价,求最小的总代价。
  • \(n\le10^5,m\le10^6\)

理清思路

题目看起来搞得人很晕,一张图上\(DP\)看起来走来走去很容易形成环,没有固定的转移顺序。

但是,仔细理一理思路,便会发现,一次转移之后时间是递增的,所以我们应该按照时间顺序来转移。

因此设\(f_{i,ti}\)表示恰好在\(ti\)时刻到达\(i\)号点所需的最小花费。(注意恰好,因为这道题对等待时间有要求)

然后只要先枚举时间,再枚举这个时间的每一条边转移即可。

斜率优化\(DP\)

考虑一条边\((x,y,p,q)\),就是要让\(f_{y,q}\)\(f_{x,i}(i\le p)\)转移。

其中\(i\le p\)的限制是很好处理的,我们原本就是在枚举时间,只要在时刻\(p\)把所有可能的\(f_{x,p}\)加入\(x\)的可选转移状态中即可。(注意,由于要求了恰好,因此可能的状态数是\(O(m)\)的)

列出一次转移的方程式:

\[f_{y,q}=\max\{A(p-i)^2+B(p-i)+C+f_{x,i}\} \]

这一看就超级斜率优化,于是我们先把转移式拆开:

\[A(p-i)^2+B(p-i)+C+f_{x,i}\\ Ap^2-2Api+Ai^2+Bp-Bi+C+f_{x,i}\\ (Ap^2+Bp+C)+(Ai^2-Bi+f_{x,i})-2Api \]

写出\(f_{x,i}\)优于\(f_{x,j}\)\(i<j\))的充要条件:

\[(Ai^2-Bi+f_{x,i})-2Api<(Aj^2-Bj+f_{x,j})-2Apj\\ 2Ap(j-i)<(Aj^2-Bj+f_{x,j})-(Ai^2-Bi+f_{x,i})\\ 2Ap<\frac{(Aj^2-Bj+f_{x,j})-(Ai^2-Bi+f_{x,i})}{j-i} \]

然后发现由于我们是按照时间转移的,\(p\)单调递增,因此只要对于每个点维护好一个斜率递增的单调队列即可。

代码:\(O(m)\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100000
#define M 1000000
#define V 40000
#define LL long long
#define INF 1e18
using namespace std;
int n,m,A,B,C;LL ans=1e18;
struct E {int x,y,t;I E(CI a=0,CI b=0,CI k=0):x(a),y(b),t(k){}};vector<E> e[V+5];vector<E>::iterator et;
struct O {int x;LL v;I O(CI i=0,Con LL& s=0):x(i),v(s){}};vector<O> o[V+5];vector<O>::iterator ot;
namespace FastIO
{
	#define FS 100000
	#define tc() (FA==FB&&(FB=(FA=FI)+fread(FI,1,FS,stdin),FA==FB)?EOF:*FA++)
	#define pc(c) (FC==FE&&(clear(),0),*FC++=c)
	int OT;char oc,FI[FS],FO[FS],OS[FS],*FA=FI,*FB=FI,*FC=FO,*FE=FO+FS;
	I void clear() {fwrite(FO,1,FC-FO,stdout),FC=FO;}
	Tp I void read(Ty& x) {x=0;W(!isdigit(oc=tc()));W(x=(x<<3)+(x<<1)+(oc&15),isdigit(oc=tc()));}
	Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
	Tp I void writeln(Ty x) {W(OS[++OT]=x%10+48,x/=10);W(OT) pc(OS[OT--]);pc('\n');}
}using namespace FastIO;
namespace Slope//斜率优化
{
	#define b(p) (1LL*A*p.ti*p.ti-1LL*B*p.ti+p.v)//计算截距
	struct P {int ti;LL v;I P(CI t=0,Con LL& s=0):ti(t),v(s){}};
	deque<P> Q[N+5];deque<P>::iterator t1,t2;
	I double S(Con P& x,Con P& y) {return x.ti^y.ti?1.0*(b(y)-b(x))/(y.ti-x.ti):(y.v>x.v?1e18:-1e18);}//计算斜率
	I void Push(CI x,Con P& p)//往x的单调队列里面加入p
	{
		if(x==n) return (void)(ans=min(ans,p.ti+p.v));if(Q[x].empty()) return Q[x].push_back(p);//x=n时统计答案
		t1=t2=--Q[x].end();W(t1!=Q[x].begin()) if(--t1,S(*t1,*t2)>=S(*t2,p)) --t2,Q[x].pop_back();else break;//弹出队尾不优元素
		Q[x].push_back(p);//加入队列
	}
	I void DP(CI x,CI y,CI p,CI q)//边(x,y,p,q)的一次转移
	{
		t1=t2=Q[x].begin(),++t2;
		W(t2!=Q[x].end()) if(S(*t1,*t2)<2*A*p) ++t1,++t2,Q[x].pop_front();else break;//弹出队首不优元素
		LL t=1LL*A*(p-t1->ti)*(p-t1->ti)+1LL*B*(p-t1->ti)+C+t1->v;o[q].push_back(O(y,t));//从队首转移
	}
}
int main()
{
	RI i,x,y,p,q;for(read(n,m,A,B,C),i=1;i<=m;++i) read(x,y,p,q),e[p].push_back(E(x,y,q));//把边扔到对应时间的桶里
	using namespace Slope;for(o[0].push_back(O(1,0)),i=0;i<=V;++i)//最初只有1号点有合法状态
	{
		for(ot=o[i].begin();ot!=o[i].end();++ot) Push(ot->x,P(i,ot->v));//把合法状态加入对应的单调队列
		for(et=e[i].begin();et!=e[i].end();++et) DP(et->x,et->y,i,et->t);//枚举边转移
	}return printf("%lld\n",ans),0;
}
posted @ 2021-04-08 09:34  TheLostWeak  阅读(44)  评论(0编辑  收藏  举报