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;
}

浙公网安备 33010602011771号