P5468 & P6302 [NOI2019] 回家路线 - 斜率优化
题解
我们将所有边按照 \(p\) 从小到大排序。显然此时每条边都只会从它前面的边转移过来。
令 \(u(e),v(e),p(e),q(e)\) 分别为边 \(e\) 的四个参数。设 \(f_e\) 为通过边 \(e\) 到达 \(v(e)\),花费的最小烦躁值(先不考虑烦躁值中单独的一项 \(q\))。所以:
\[f_e=\min_{v(e')=u(e)\land q(e')\le p(e)} \{f_{e'}+A(p(e)-q(e'))^2+B(p(e)-q(e'))+C \}
\]
答案即为 \(\min_{v(e)=n} f_e+q_e\)。
我们将 \(f\) 的转移方程整理成斜率优化的形式:
\[f_e=\min_{v(e')=u(e)\land q(e')\le p(e)} \{f_{e'}+Aq^2(e')-Bq(e')-2Aq(e')p(e) \}+Ap^2(e)+Bp(e)+C
\]
我们维护 \(n\) 个凸壳,第 \(i\) 个凸壳内的任意点 \(x\) 都满足 \(v(x)=i\)。因为 \(p(e)\) 单调递增,所以可以线性地维护这个凸壳。当我们计算完一个 \(f_e\),我们先将它放在下标为 \(q(e)\) 的桶里,当我们要计算一个 \(f_{e'}\) 满足 \(p(e')\ge q(e)\) 时,再把这些桶里的点都放到凸壳里。
复杂度 \(\mathcal{O}(n+m)\)。
代码
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
#include <deque>
using namespace std;
#define For(Ti,Ta,Tb) for(int Ti=(Ta);Ti<=(Tb);++Ti)
#define Dec(Ti,Ta,Tb) for(int Ti=(Ta);Ti>=(Tb);--Ti)
#define Debug(...) fprintf(stderr,__VA_ARGS__)
typedef long long ll;
typedef __int128 i128;
const int N=2e5+5,M=2e6+5;
const ll Inf=0x3f3f3f3f3f3f3f3f;
int n,m;ll A,B,C;
struct Edge{
int u,v;ll p,q;
}edge[M];
ll f[M];
auto X=[](int i){return edge[i].q;};
auto Y=[](int i){return f[i]+A*edge[i].q*edge[i].q-B*edge[i].q;};
bool Pop(int x,int y,int z){
return i128(Y(y)-Y(x))*(X(z)-X(y))>=i128(Y(z)-Y(y))*(X(y)-X(x));
}
ll W(int x,ll k){return f[x]+A*edge[x].q*edge[x].q-B*edge[x].q-2*A*k*edge[x].q;}
deque<int> dq[N];
vector<int> buk[N];
int main(){
#ifndef zyz
ios::sync_with_stdio(false),cin.tie(nullptr);
#endif
cin>>n>>m>>A>>B>>C;
For(i,1,m){
cin>>edge[i].u>>edge[i].v>>edge[i].p>>edge[i].q;
}
sort(edge+1,edge+m+1,[](const Edge &e1,const Edge &e2){return e1.p<e2.p;});
int cur=0;
memset(f,0x3f,sizeof f);
For(i,1,m){
int u=edge[i].u;ll p=edge[i].p,q=edge[i].q;
if(u==1){
f[i]=A*p*p+B*p+C;
}
for(;cur<=p;++cur){
for(int e:buk[cur]){
int x=edge[e].v;
while(dq[x].size()>1&&Pop(dq[x][dq[x].size()-2],dq[x].back(),e)) dq[x].pop_back();
dq[x].push_back(e);
}
buk[cur].clear();
}
while(dq[u].size()>1&&W(dq[u].front(),p)>=W(dq[u][1],p)) dq[u].pop_front();
if(dq[u].size()) f[i]=min(f[i],W(dq[u].front(),p)+A*p*p+B*p+C);
buk[q].push_back(i);
}
ll ans=Inf;
For(i,1,m) if(edge[i].v==n) ans=min(ans,edge[i].q+f[i]);
cout<<ans;
return 0;
}
坑点
若一条边 \(e\) 满足 \(p(e)=1\),那直接让 \(f_e=Ap^2(e)+Bp(e)+C\) 不一定是最优的,可能绕一个圈回来。
Written by Alan_Zhao