1367: [Baltic2004]sequence
Time Limit: 20 Sec Memory Limit: 64 MB
Description
Input
Output
一个整数R
Sample Input
7
9
4
8
20
14
15
18
Sample Output
13
HINT
所求的Z序列为6,7,8,13,14,15,18.
R=13
为了方便,我们先解决所求数列不递减的情况。
首先由这么一件事情:对若干数确定一个数v,当v时这些数的中位数(为了方便,n个数的中位数我们定义为它们从小到大排序后第
⌈n2⌉
个数),这些数与v的差的绝对值之和是最小的。关于证明可以将这些数画在数轴上,利用绝对值的几何意义即可。
再来考虑这样一件事情:对于序列中连续的两段,若这两段的中位数分别对应这两段的最优解(设为u和v),那么当u<=v的时候,平安无事;否则,我们要将这两段进行合并。直觉告诉我们,合并之后的新区间的最优解应该是新区间的中位数。 事实也是如此。证明就留给读者思考了=w=那么算法便不难构思了:我们把解相同一段序列划作一段。当加进来一个数之后,把先令它的位置对应的解等于它自己本身,之后不断地与前一段进行比较,若当前段的值小于前一段,则需要将它们合并,并将新的解设为它们的中位数。这样我们便能得到最优解的一个可行解。
可以看出我们需要一个能够支持合并两个相邻区间并维护中位数的数据结构。使用平衡树显然是可以的,但是由于常数问题,在这道题的数据规模下难以通过。继续优化:我们可以只保存区间中较小一半的(即前
⌈n2⌉
)的数,查询中位数便变成了查询最大值。并且不难发现,当且仅当合并的两个区间的长度都为奇数时,我们需要删掉最大的数。显然可并堆是一个很好的选择。相比编程复杂度较高的二项堆和斐波那契堆,我采用的是性价比较高的左偏树。
上面解决了所求序列不递减的问题。如何做到递增呢?有一个巧妙的办法:对于初始的数列,我们统一减去它的序号即可(即ai赋值为ai-i)。
#include<cstdio>
#include<iostream>
#include<algorithm>
#define maxn 1000000
using namespace std;
struct leftisttree{
struct treenode{
int v,l,r,d;
treenode(){d=l=r=v=0;}
bool operator<(const treenode&a)const{return v<a.v;}
};
treenode t[maxn+10];
int merge(int a,int b){
if(a==0||b==0)return a+b;
if(t[a]<t[b])swap(a,b);
t[a].r=merge(t[a].r,b);
if(t[t[a].l].d<t[t[a].r].d)swap(t[a].l,t[a].r);
t[a].d=t[a].r?t[t[a].r].d+1:0;
return a;
}
int root[maxn+10],m,sz[maxn+10];
void solve(int n){
t[0].d=-1;
for(int i=1;i<=n;i++){
scanf("%d",&t[i].v);
t[i].v-=i;
root[++m]=i;
sz[m]=1;
while(m>1&&t[root[m]].v<t[root[m-1]].v){
int k=sz[m]&sz[m-1]&1;
root[m-1]==merge(root[m-1],root[m]);
if(k)root[m-1]=merge(t[root[m-1]].l,t[root[m-1]].r);
sz[m-1]+=sz[m];
m--;
}
}
int cur=1;
long long ans=0;
for(int i=1;i<=m;i++)
for(int j=1;j<=sz[i];j++,cur++)ans+=abs(t[root[i]].v-t[cur].v);
cout<<ans<<endl;
}
}ltt;
int main(){
int n;
scanf("%d",&n);
ltt.solve(n);
return 0;
}