luogu P6302 [NOI2019] 回家路线 加强版
题面传送门
不得不说原题数据是真的水,先是一个负号没打,然后数据范围看错还过。
首先为了确保一定的单调性,要把所有边按开始时间排序。这个可以用桶排。
然后设\(dp_i\)为走了第\(i\)条边最小的烦躁值。
那么显然有方程式\(dp_i=\min\limits_{y_j=x_i,q_j\leq p_i}{dp_j+A(p_i-q_j)^2+B(p_i-q_j)+C}\)
这个东西显然可以斜率优化,设\(q_j<q_k\)且\(j<k\),设\(X_i=dp_i+Aq_i^2-B q_i\)那么有\(\frac{X_j-X_k}{q_j-q_k}<2p_i\)
这个东西就是斜率优化形式。开\(n\)个单调队列维护即可。
但是我们按照起始时间排序只能保证后面的不降而不能保证前面的不降。
所以当我们处理好一条边时我们并不急于将这条边放到单调队列中,而是再开一个堆,然后枚举到一个起始时间时将终止时间小于等于当前时间的放入单调队列,就可以保证左边单调不降。
因为域值很小所以堆可以用vector代替。
时间复杂度\(O(n+m)\),代码很好写。
code:
#include<cstdio>
#include<vector>
#include<queue>
#include<cstring>
#define ll long long
#define lb long double
#define N 1000039
#define W 40039
#define eps 1e-7
#define I inline
#define X(a) (dp[a]+A*b[a]*b[a]-B*b[a])
#define min(a,b) ((a)<(b)?(a):(b))
using namespace std;
int n,m,k,x[N],y[N],a[N],b[N],z,head[N],tail[N],now;ll A,B,C,dp[N],ans=1e18;
vector<int> f[W],g[W],q[N];
I lb slope(int x,int y){return (lb)(X(x)-X(y))/(b[x]==b[y]?-eps:(b[x]-b[y]));}
I void read(int &x) {
char s=getchar();x=0;
while(s<'0'||s>'9')s=getchar();
while(s>='0'&&s<='9') x=x*10+s-48,s=getchar();
}
int main(){
freopen("1.in","r",stdin);
register int i,j,h,k;scanf("%d%d%lld%lld%lld",&n,&m,&A,&B,&C);memset(dp,0x3f,sizeof(dp));dp[0]=0;
for(i=1;i<=m;i++)read(x[i]),read(y[i]),read(a[i]),read(b[i]),f[a[i]].push_back(i);
for(i=2;i<=n;i++) tail[i]=-1;q[1].push_back(0);y[0]=1;
for(i=0;i<W;i++){
for(j=0;j<g[i].size();j++){
h=g[i][j];now=y[h];while(head[now]<tail[now]&&slope(q[now][tail[now]-1],q[now][tail[now]])>slope(q[now][tail[now]],h)) tail[now]--;
tail[now]++;(tail[now]==q[now].size())?(q[now].push_back(h),0):(q[now][tail[now]]=h);
}
for(j=0;j<f[i].size();j++){
h=f[i][j];now=x[h];while(head[now]<tail[now]&&slope(q[now][head[now]],q[now][head[now]+1])<2*A*i) head[now]++;
(q[now].size())&&(k=q[now][head[now]],dp[h]=dp[k]+A*(i-b[k])*(i-b[k])+B*(i-b[k])+C,g[b[h]].push_back(h),0);
}
}
for(i=1;i<=m;i++)(y[i]==n)&&(ans=min(ans,dp[i]+b[i]));printf("%lld\n",ans);
}