把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【BZOJ1367】[Baltic2004] sequence(左偏树维护中位数)

点此看题面

大致题意: 给定一个序列\(a_i\),求一个递增序列\(b_i\)使得\(\sum_{i=1}^n|a_i-b_i|\)最小。

递增\(\rightarrow\)不降

显然这题中递增序列这个条件让人很难受。

因此,我们对于每个\(a_i\),给它减去一个\(i\)\(b_i\)同理),这样一来\(b_i\)的递增条件就可以改为不降了。

中位数

考虑最终的答案肯定是由一段段相同的数组成的。

对于每一段,我们根据初一的数学知识可以知道,对于若干数,要找出一个数使得差值绝对值之和最小,必然是其中位数。

于是我们就有一个大致思路了:每次加入一个数,我们记下它的中位数(初始是它自身),然后如果这个中位数小于前一段的中位数(不满足递增条件了),我们就合并两段,并记录下新的中位数。

如此不断搞下去,我们就会发现这相当于是单调栈的过程。

但是,该如何维护一段数的中位数呢?

左偏树

考虑我们只要开个大根堆存储区间中前一半的数,则堆顶自然就是中位数了。

然后合并两段数,我们只要合并两个堆,然后若此时堆中数的数量大于一半,就弹出堆顶,即可实现中位数的维护。

而要合并堆,自然就需要左偏树啦!

可以说这题思想还是挺不错的吧。

代码

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 1000000
#define swap(x,y) (x^=y^=x^=y)
using namespace std;
int n,a[N+5],S[N+5],P[N+5];
class FastIO
{
	private:
		#define FS 100000
		#define tc() (A==B&&(B=(A=FI)+fread(FI,1,FS,stdin),A==B)?EOF:*A++)
		#define pc(c) (C==E&&(clear(),0),*C++=c)
		#define D isdigit(c=tc())
		int T;char c,*A,*B,*C,*E,FI[FS],FO[FS],S[FS];
	public:
		I FastIO() {A=B=FI,C=FO,E=FO+FS;}
		Tp I void read(Ty& x) {x=0;W(!D);W(x=(x<<3)+(x<<1)+(c&15),D);}
		Tp I void write(Ty x,Con char& y) {W(S[++T]=x%10+48,x/=10);W(T) pc(S[T--]);pc(y);}
		I void clear() {fwrite(FO,1,C-FO,stdout),C=FO;}
		#undef D
}F;
class LeftistTree//左偏树
{
	private:
		struct node {int Sz,D,S[2];}O[N+5];
	public:
		I int operator [] (CI x) Con {return O[x].Sz;} 
		I void Init() {for(RI i=1;i<=n;++i) O[i].Sz=1;}
		I int Merge(RI x,RI y)//合并
		{
			if(!x||!y) return x|y;a[x]<a[y]&&swap(x,y),
			O[O[x].S[1]=Merge(O[x].S[1],y)].D>O[O[x].S[0]].D&&swap(O[x].S[0],O[x].S[1]);
			return O[x].Sz=O[O[x].S[0]].Sz+O[O[x].S[1]].Sz+1,O[x].D=O[O[x].S[1]].D+1,x;
		}
		I int Pop(CI x) {return Merge(O[x].S[0],O[x].S[1]);}//弹出堆顶
}L;
int main()
{
	RI i,j;for(F.read(n),i=1;i<=n;++i) F.read(a[i]),a[i]-=i;//减去下标以改递增为不降
	RI k,T=0;for(L.Init(),i=1;i<=n;++i)
	{
		k=i;W(T&&a[k]<a[S[T]]) k=L.Merge(k,S[T--]),L[k]>(i-P[T]+1>>1)&&(k=L.Pop(k));//单调栈,用左偏树维护中位数
		S[++T]=k,P[T]=i;//加入栈中
	}
	long long ans=0;for(i=j=1;i<=n;++i) ans+=abs(a[i]-a[S[j]]),P[j]==i&&++j;
	return printf("%lld",ans),0;
//	洛谷需要输出b。。。
//	for(F.write(ans,'\n'),i=j=1;i<=n;++i) F.write(a[S[j]]+i," \n"[i==n]),P[j]==i&&++j;
//	return F.clear(),0;
}
posted @ 2020-05-21 15:46  TheLostWeak  阅读(171)  评论(0编辑  收藏  举报