Loading

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\) 不一定是最优的,可能绕一个圈回来。

posted @ 2022-01-18 13:37  Alan_Zhao_2007  阅读(36)  评论(0编辑  收藏  举报