• 洛谷 P6302


    洛谷题目页面传送门

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

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

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

    [dp_i=minlimits_{y_j=x_i,q_jleq p_i}!left{dp_j+a(p_i-q_j)^2+b(p_i-q_j)+c ight} ]

    直接转移是(mathrm O!left(n^2 ight))的,考虑优化。容易想到的是,我们可以按(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 ight)-left(dp_k+aq_k^2-bq_k ight)<2ap_i(q_j-q_k) ]

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

    [frac{left(dp_j+aq_j^2-bq_j ight)-left(dp_k+aq_k^2-bq_k ight)}{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)=egin{cases}+infty&dp_j<dp_k\0&dp_j=dp_k\-infty&dp_j>dp_kend{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_ileq p_j),又(p_j<q_j),所以(q_j>p_i),所以(i)不可能转移到(j),得证。

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

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

    什么?你问我到底用哪种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;
    }
    
  • 相关阅读:
    .NET基础——数组
    .NET基础——循环、枚举
    .NET基础——运算符
    .NET基础——基本概念
    面向对象基础——结构体
    面向对象基础——静态成员、静态类
    面向对象基础——基本概念
    接口 与 抽象类
    委托与事件——事件
    委托与事件——委托
  • 原文地址:https://www.cnblogs.com/ycx-akioi/p/Luogu-P6302.html
Copyright © 2020-2023  润新知