LOJ #3156. 「NOI2019」回家路线

题目叙述

给定 \(n\) 个点 \(m\) 条边的有向图,一个人要从 1 走到 \(n\) ,每条边上有一些列车,列车从 \(p_i\) 时刻出发,\(q_i\) 时刻走到终点。一个人的烦躁值定义为他等待时间 \(t\)\(At^2+Bt+C\) 的总和与到达时间之和,问最小烦躁值是多少。

题解

一种简单的动态规划是设 \(f_i\) 表示走到第 \(i\) 条边的终点烦躁值最小是多少。
枚举这条边的起始节点就可以转移。
但是这样复杂度是一个点入度与出度乘积之和的,不可以接受。
那怎么办,发现这个代价是二次函数,直接斜率优化,每次计算一个节点的代价相当于凸包上查询,计算完了代价把他插入到他终点的凸包上就可以了。
复杂度 \(\mathcal O(M)\) 或者 \(\mathcal O(M\log M)\) 。视实现而定。

总结

没有。但是感觉一些模板套在一起的题写起来比较顺畅。

代码

我写的是二分,带个 \(\log\) ,不过问题不大。
写的时候注意两点:

  • 斜率优化需要注意除 0 的情况。
  • 一条边从起点出发不一定最小值就是直接走这条边,说不定是绕个圈子回来再走这条边。
#include <cstdio>
#include <iostream>
#include <vector>
#include <cstring>
#include <algorithm>
#define macro_expand(x) #x
#define print_macro(x) printf("%s\n",macro_expand(x))
#define FOR(i,l,r) for(int i=(l),i##ADJK=(r);i<=i##ADJK;++i)
#define ROF(i,r,l) for(int i=(r),i##ADJK=(l);i>=i##ADJK;--i)
using namespace std;
typedef long long LL;
template<typename T>bool chkmax(T &x,const T y){return (x<y)?(x=y,1):0;}
template<typename T>bool chkmin(T &x,const T y){return (x>y)?(x=y,1):0;}
const int MN=1e5+5,MM=1e6+5;
int N,M;
LL A,B,C;
struct E{
	int x,y;
	LL a,b;
	E():x(0),y(0),a(0),b(0){}
	E(int _x,int _y,LL _a,LL _b):x(_x),y(_y),a(_a),b(_b){}
}edge[MM];
bool cmp1(const E &p,const E &q){return p.a<q.a;}
LL f[MM];
struct point{
	LL x,y;
	point():x(0),y(0){}
	point(LL _x,LL _y):x(_x),y(_y){}
};
point operator+(const point &a,const point &b){return point(a.x+b.x,a.y+b.y);}
double slope(const point &a,const point &b){
	if(b.x==a.x){
		if(b.y>a.y)return 1e18;
		else return -1e18;
	}
	return 1.0*(b.y-a.y)/(b.x-a.x);
}
struct convex{
	vector<pair<point,int> > node;
	#define siz node.size()
	#define fi first
	#define se second
	void add(point x,int id){
		while(node.size()>=2&&slope(node[siz-2].fi,node[siz-1].fi)>slope(node[siz-1].fi,x))
			node.pop_back();
		node.push_back(make_pair(x,id));
	}
	int query(LL x){
		if(node.size()==0)return -1;
		int l=0,r=node.size()-2,ret=node.back().se;
		while(l<=r){
			int mid=(l+r)>>1;
			if(slope(node[mid].fi,node[mid+1].fi)>=2*A*x)ret=node[mid].se,r=mid-1;
			else l=mid+1;
		}
		return ret; // 下标
	}
}con[MN];
int id[MM];
bool cmp2(const int &x,const int &y){return edge[x].b<edge[y].b;}
int main(){
	freopen("route.in","r",stdin);
	freopen("route.out","w",stdout);
	scanf("%d%d%lld%lld%lld",&N,&M,&A,&B,&C);
	FOR(i,1,M){
		scanf("%d%d%lld%lld",&edge[i].x,&edge[i].y,&edge[i].a,&edge[i].b);
	}
	memset(f,0x3f,sizeof(f));
	sort(edge+1,edge+M+1,cmp1);
	LL ans=1e18;
	FOR(i,1,M)id[i]=i;
	sort(id+1,id+M+1,cmp2);
	int pt=1;
	FOR(i,1,M){
		int tmp=0;
		while(pt<=M&&edge[tmp=id[pt]].b<=edge[i].a){ // <=
			con[edge[tmp].y].add(point(edge[tmp].b,f[tmp]+A*edge[tmp].b*edge[tmp].b-B*edge[tmp].b),tmp);
			++pt;
		}
		if(edge[i].x==1)f[i]=(LL)A*edge[i].a*edge[i].a+(LL)B*edge[i].a+C;
		// 是说可能绕一圈回来??????
		int p=con[edge[i].x].query(edge[i].a);
		int x=edge[i].a;
		if(p!=-1)chkmin(f[i],f[p]+(LL)A*(x-edge[p].b)*(x-edge[p].b)+(LL)B*(x-edge[p].b)+C);
		if(edge[i].y==N)chkmin(ans,f[i]+edge[i].b);
	}
	printf("%lld\n",ans);
	fclose(stdin);
	fclose(stdout);
	return 0;
}
posted @ 2022-07-27 21:12  YouthRhythm  阅读(76)  评论(0)    收藏  举报