洛谷 P6302 - 回家路线 加强版

洛谷题目页面传送门

题意见洛谷里的翻译。(用\(a,b,c\)分别表示\(A,B,C\)

直接考虑DP。设\(dp_i\)表示最后一次坐的是列车\(i\)时的最小烦躁值。显然,边界是\(dp_0=0\)(同时假装\(y_0=1\)),目标是\(\min\limits_{y_i=n}\{q_i+dp_i\}\)

考虑转移。可转移性显然,因为\(2\)个状态不可能互为决策,即决策->状态构成的有向图一定是DAG。于是状态转移方程显然为

\[dp_i=\min\limits_{y_j=x_i,q_j\leq p_i}\!\left\{dp_j+a(p_i-q_j)^2+b(p_i-q_j)+c\right\} \]

直接转移是\(\mathrm O\!\left(n^2\right)\)的,考虑优化。容易想到的是,我们可以按\(y\)值建桶,这样每个状态的决策仅在\(1\)个桶内。

接下来,这个转移方程是一个非常经典的斜率优化模型,上手直接干:

对于\(2\)个决策\(j,k\)\(j\)\(k\)优当且仅当

\[dp_j+a(p_i-q_j)^2+b(p_i-q_j)+c<dp_k+a(p_i-q_k)^2+b(p_i-q_k)+c \]

化简得

\[dp_j+aq_j^2-bq_j-2ap_iq_j<dp_k+aq_k^2-bq_k-2ap_iq_k \]

将仅关于决策变量的项放左边,既关于状态变量又关于决策变量的项放右边,得

\[\left(dp_j+aq_j^2-bq_j\right)-\left(dp_k+aq_k^2-bq_k\right)<2ap_i(q_j-q_k) \]

要除过来了,可是我们还没确定\(q_j-q_k\)的符号。于是我们强行令\(q_j\leq q_k\)。先考虑\(q_j<q_k\),此时\(q_j-q_k<0\),除过去,变不等号方向,得

\[\frac{\left(dp_j+aq_j^2-bq_j\right)-\left(dp_k+aq_k^2-bq_k\right)}{q_j-q_k}>2ap_i \]

于是左边可以抽象成在以\(q_i\)为点\(i\)的横坐标、以\(dp_i+aq_i^2-bq_i\)为点\(i\)的纵坐标的直角坐标系中,过点\(j\)与点\(k\)的直线的斜率。我们设这个斜率为\(f(j,k)\)。那么问题来了,\(q_j=q_k\)\(q_j-q_k=0\)时没有斜率怎么办呢?我们可以按照它与\(2ap_i\)比较之后必须得出正确的优劣关系的原则,规定当\(q_j=q_k\)\(f(j,k)=\begin{cases}+\infty&dp_j<dp_k\\0&dp_j=dp_k\\-\infty&dp_j>dp_k\end{cases}\)

然后套路化地发现,若\(3\)个决策\(i<j<k\)满足\(f(i,j)\geq f(j,k)\),分\(2\)种情况:

  1. \(f(i,j)>2ap_i\):此时\(i\)\(j\)优;
  2. \(f(i,j)\leq 2ap_i\):结合\(f(i,j)\geq f(j,k)\)可得\(f(j,k)\leq 2ap_i\),此时\(j\)没有\(k\)优。

综上,\(j\)一定不是最优决策,于是可以把它扔掉,即维护一个斜率严格单调上升的上凸壳。由于有\(n\)个桶作为决策栖息地,我们维护每个桶内的上凸壳,然后计算DP值时在对应的桶内的上凸壳内找即可。

接下来考虑DP顺序。显而易见的有\(2\)种:按\(p\)值升序和按\(q\)值升序。两种正确性都很好证,以\(p\)值升序为例,对于任意\(i<j\),有\(p_i\leq p_j\),又\(p_j<q_j\),所以\(q_j>p_i\),所以\(i\)不可能转移到\(j\),得证。

先考虑\(q\)值升序。此时每个上凸壳可以单调栈\(stk\)维护。每计算完一个DP值,由于横坐标非严格单调上升,可以直接在栈顶加点并维护单调性(好吧其实并不能,还是得像下面那样two-pointers,因为转移方程\(\min\)里有\(q_j\leq p_i\)的限制)。计算的时候嘛,就只能二分查找第一个满足\(f(stk_j,stk_{j+1})>2ap_i\)\(stk_j\)作为最优决策了(正确性显然)。单DP的时间复杂度就\(\mathrm O(m\log m)\)

再来考虑\(p\)值升序。\(2ap_i\)显然也非严格单调递增,可以将单调栈改进为单调队列。但是这样刚计算完DP值并不能直接加进单调队列尾,因为不能保证没有其他横坐标更小的还没计算。所以应该对于每个上凸壳维护two-pointers,计算每个DP值时,在决策所在上凸壳的单调队列尾一连按\(q\)值升序添加好几个点。DP时间复杂度\(\mathrm O(m)\)。但是按\(p\)值、\(q\)值排序什么的还是带\(\log\)啊,这么一来费尽心思去DP的\(\log\)不是没用了吗?别急,可以桶排。时间复杂度\(\mathrm O\!\left(n+m+\max\limits_{i=1}^n\{q_i\}\right)\)

什么?你问我到底用哪种DP顺序?当然用下面那种复杂度小的啦

另外,这题比较卡常,我O3、快读、改dequevector模拟队列三连才AC。

代码:

#pragma GCC optimize(3)
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define y0 afejaogoea
#define pb push_back
const int inf=0x3f3f3f3f3f3f3f3f;
const double dinf=1e100;
void read(int &x){
	x=0;char c=getchar();
	while(!isdigit(c))c=getchar();
	while(isdigit(c))x=(x<<1)+(x<<3)+(c^48),c=getchar();
}
int sq(int x){return x*x;}
const int N=100000,M=1000000,Q_I=40000;
int n,m,a,b,c;
int x[M+1],y[M+1],p[M+1],q[M+1];
vector<int> buc_y[N+1],buc_p[Q_I+1],buc_q[Q_I+1];//桶们 
int now[N+1];//two-pointers变量 
vector<int> que[N+1];//vector模拟队列 
int head[N+1],tail[N+1];//vector模拟队列 
int dp[M+1];//DP数组 
double f(int j,int k){//斜率 
	int y_j=dp[j]+a*sq(q[j])-b*q[j],y_k=dp[k]+a*sq(q[k])-b*q[k];
	if(q[j]==q[k])return y_j<y_k?dinf:y_j==y_k?0:-dinf;
	return 1.*(y_j-y_k)/(q[j]-q[k]);
}
signed main(){
//	freopen("C:\\Users\\chenx\\OneDrive\\桌面\\txt.txt","r",stdin);
	read(n);read(m);read(a);read(b);read(c);
	for(int i=1;i<=m;i++)read(x[i]),read(y[i]),read(p[i]),read(q[i]),buc_p[p[i]].pb(i)/*桶排*/,buc_q[q[i]].pb(i)/*桶排*/;
	buc_y[1].pb(0);
	for(int i=0;i<=Q_I;i++)for(int j=0;j<buc_q[i].size();j++)buc_y[y[buc_q[i][j]]].pb(buc_q[i][j]);//装桶 
	for(int i=1;i<=n;i++)que[i].resize(buc_y[i].size());
	memset(now,-1,sizeof(now));
	for(int i=0;i<=Q_I;i++)for(int j=0;j<buc_p[i].size();j++){//转移 
		int x0=buc_p[i][j];
		while(now[x[x0]]+1<buc_y[x[x0]].size()&&q[buc_y[x[x0]][now[x[x0]]+1]]<=p[x0]){//一连往队尾加好几个点 
			int y0=buc_y[x[x0]][++now[x[x0]]];
			while(head[x[x0]]+1<tail[x[x0]]&&f(que[x[x0]][tail[x[x0]]-2],que[x[x0]][tail[x[x0]]-1])>=f(que[x[x0]][tail[x[x0]]-1],y0))tail[x[x0]]--;//维护斜率单调性 
			que[x[x0]][tail[x[x0]]++]=y0;
		}
		while(head[x[x0]]+1<tail[x[x0]]&&f(que[x[x0]][head[x[x0]]],que[x[x0]][head[x[x0]]+1])<2*a*p[x0])head[x[x0]]++;//从队首弹出 
		if(head[x[x0]]<tail[x[x0]]){
			int y0=que[x[x0]][head[x[x0]]];
			dp[x0]=dp[y0]+a*sq(p[x0]-q[y0])+b*(p[x0]-q[y0])+c;
		}
		else dp[x0]=inf;//计算DP值 
	}
	int ans=inf;
	for(int i=0;i<buc_y[n].size();i++)ans=min(ans,q[buc_y[n][i]]+dp[buc_y[n][i]]);//目标 
	cout<<ans;
	return 0;
}

猜你喜欢

转载自www.cnblogs.com/ycx-akioi/p/Luogu-P6302.html
今日推荐