- 有一张(n)个点的图和三个参数(A,B,C),你需要从(1)号点走到(n)号点。
- 有(m)条有向边,每条边有一个出发时间和到达时间,只有在出发时间之前到达出发点才能选择走这条边。
- 走两条边的间隔可能存在等待时间,假设一次等待时间为(x),则需要花费(Ax^2+Bx+C)的代价。
- 假设你在(t)时刻到达(n)号点,还需要额外花费(t)的代价,求最小的总代价。
- (nle10^5,mle10^6)
理清思路
题目看起来搞得人很晕,一张图上(DP)看起来走来走去很容易形成环,没有固定的转移顺序。
但是,仔细理一理思路,便会发现,一次转移之后时间是递增的,所以我们应该按照时间顺序来转移。
因此设(f_{i,ti})表示恰好在(ti)时刻到达(i)号点所需的最小花费。(注意恰好,因为这道题对等待时间有要求)
然后只要先枚举时间,再枚举这个时间的每一条边转移即可。
斜率优化(DP)
考虑一条边((x,y,p,q)),就是要让(f_{y,q})从(f_{x,i}(ile p))转移。
其中(ile 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('
');}
}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
",ans),0;
}