洛谷 P4272 - [CTSC2009]序列变换(堆)

洛谷题面传送门

u1s1 在我完成这篇题解之前,全网总共两篇题解,一篇使用的平衡树,一篇使用的就是这篇题解讲解的这个做法,但特判掉了一个点,把特判去掉在 BZOJ 上会 WA 一个点。

两篇题解都异常简略,前一篇题解甚至只有代码没有说明,所以这里我来写篇比较详细题解造福人类了(大雾

建议没做过这题的同学先看看这道题,看题解区第一个做法,对这题有比较大的启发。

注:下文中分别用 \(a,b,l,r\) 代替题面中的 \(X,Y,A,B\)

首先关于这题咱们可以想到一个非常 naive 的 DP,\(dp_{i,j}\) 表示考虑到前两个数,\(b_i=j\)\(\sum\limits_{k=1}^i|a_k-b_k|\)。那么显然有 DP 转移 \(dp_{i,j}=\min\limits_{k=l}^rdp_{i-1,j-k}+|a_i-j|\)。一脸不好直接维护的样子。不过注意到绝对值函数是一个下凸函数,因此我们猜测对于一个固定的 \(i\)\(dp_{i,j}\)\(j\)​ 的增大也是一个下凸函数,事实也的确如此,证明大概可以归纳,可能需要一些分类讨论,这里不再赘述,留给读者自己思考。

我们还可以注意到,每一轮中每条直线斜率变化的绝对值最多为 \(1\)​​,也就是说任意时刻,下凸壳中直线斜率绝对值的最大值顶多为 \(n\)​​,因此我们考虑维护 \(2n+2\)​​ 个分界点的横坐标,具体来说,记 \(x_i\)​​ 为第 \(i\)​​ 个分界点,那么以凸包上横坐标分别为 \(x_i,x_{i+1}\)​​ 的点组成线段刚好是凸包上斜率为 \(i-n-2\)​​ 的直线,当然也有可能 \(x_i=x_{i+1}\)​​,此时凸包上不存在斜率为 \(i-n-2\)(说白了就是记录凸包拐点的横坐标)。

接下来考虑加入一个 \(a_i\)​​ 后会对凸包产生怎样的影响。我们考虑将每一轮转移的过程分成两部分:\(dp_{i,j}=\min\limits_{k=l}^rdp_{i-1,j-k}\)​ 和 \(dp_{i,j}:=dp_{i,j}+|a_i-j|\)​,首先对于第一部分而言,显然在凸包上存在一些分界点,满足分界点前面单调不增,分界点之后单调不降,不难发现这些分界点组成的集合就是斜率为 \(0\)​ 的直线。假设斜率为 \(0\)​ 的直线的两个端点横坐标为 \(u,v(u,v)\)​,那么不难发现对于 \(j\le u+l\),由于 \(u\) 前面单调递减,因此我们肯定会尽量选择 \(j\) 与接近的点转移,即 \(dp_{i,j-l}\),因此我们可以得到,进行第一步操作后,凸包上 \(u\) 前面的点都会向右平移 \(l\) 格,同理在 \(v+r\) 后面的点 \(j\),我们肯定会尽量选离 \(j\) 远的点的即 \(j-r\),也就是说 \(v\)​ 后面的点肯定会向右平移 \(r\) 格,至于中间的部分……那显然还是一条水平的直线咯(

第二部分就相对比较简单了,不难发现 \(f(x)=|a_i-x|\)\((-\infty,a_i)\) 中是斜率为 \(-1\) 的直线,\((a_i,\infty)\)​ 中是斜率为 \(1\) 的直线,因此 \(a_i\)​ 前面的部分的直线的斜率会减 \(1\),后面的部分斜率会加 \(1\)。这样我们可以看作是在 \(a_i\) 处插入了两个断点。

考虑怎样维护这个东西,我们开两个堆 \(L,R\) 分别存储斜率为 \(0\) 的直线前面和后面的断点,那么时刻 \(i\) 时显然 \(L\) 应当为存储的是最小的 \(i\) 个断点,\(R\) 应当存储最大的 \(i\) 个断点,但有时并不一定真的存储的就是最小/最大的 \(i\) 个断点,这时候我们就要进行调整,具体方法就是取出 \(L\) 中最大的元素 \(x\) 和最小的元素 \(y\),如果 \(x>y\) 就将 \(x\) 插入 \(R\)\(y\) 插入 \(L\),否则 break。还有一个问题就是如何处理坐标平移,具体方法就在 \(i\) 时刻是将 \(x\) 插入 \(L\) 时我们不插入 \(x\) 本身,而是插入 \(x-(i-1)l\),这样在 \(i’\) 时刻这个点真正的坐标就是 \(x+i’l\),不难发现这里用了差分的思想,在统计每个点有多少个时刻满足 xxxx 条件时也用到了类似的思想。

时间复杂度 \(n\log n\)

注意加一些特判,否则在 BZOJ 上会 WA 一个点,进而取得 \(0\) 分的好成绩(London Fog

const int MAXN=5e5;
int n,mx,l,r,a[MAXN+5],b[MAXN+5];ll ext=0;
priority_queue<ll> L;
priority_queue<ll,vector<ll>,greater<ll> > R;
//void prt(auto q){
//	while(!q.empty()) printf("%d ",q.top()),q.pop();
//	printf("\n");
//}
int main(){
	scanf("%d%d%d%d",&n,&mx,&l,&r);
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
		if(a[i]<1ll*(i-1)*l+1){
			ext+=abs(a[i]-(1ll*(i-1)*l+1));
			a[i]=1ll*(i-1)*l+1;
		}
	}
	for(int i=1;i<=n;i++){
		L.push(a[i]-1ll*(i-1)*l);
		R.push(a[i]-1ll*(i-1)*r);
		while(1){
			ll x=L.top();x+=1ll*(i-1)*l;
			ll y=R.top();y+=1ll*(i-1)*r;
//			printf("%lld %lld\n",x,y);
			if(x<=y) break;L.pop();R.pop();
			L.push(y-1ll*(i-1)*l);R.push(x-1ll*(i-1)*r);
		} b[i]=L.top()+1ll*(i-1)*l;
//		prt(L);prt(R);
	} ll res=0;chkmin(b[n],mx);
    chkmax(b[n],1ll*l*(n-1)+1);//be careful
	for(int i=n-1;i;i--){
		if(b[i]<b[i+1]-r) b[i]=b[i+1]-r;
		if(b[i]>b[i+1]-l) b[i]=b[i+1]-l;
	}
//	if(n==8888) for(int i=1;i<=n;i++) printf("%d%c",b[i]," \n"[i==n]);
	for(int i=1;i<=n;i++) res+=abs(a[i]-b[i]);
	printf("%lld\n",res+ext);
	return 0;
}
posted @ 2021-08-14 17:36  tzc_wk  阅读(56)  评论(0编辑  收藏  举报