BZOJ 5059: 前鬼后鬼的守护 可并堆 左偏树 数学

https://www.lydsy.com/JudgeOnline/problem.php?id=5059

题意:将原序列{ai}改为一个递增序列{ai1}并且使得abs(ai-ai1)的和最小。

如果一个数列是递增的则不予考虑,如果是递减的,那么应该将这个递减序列每一个数都修改为这个序列的中位数(如果中位数是两数平均数则两数间任意一数都可以),手推一下可以知道这个性质的正确性。

因为后面的(中位)数小才会向前合并,所以新的中位数一定在前面或后面数列从小到大排序后的前半段(包含中位数)中(后面数列比中位数大的部分(不在堆中)一定比前面数列的中位数大),维护一个大根可并堆存从小到大排序后区间内的后半段数然后向前合并就能得到新的中位数。

嗯不错的题?

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<algorithm>
 4 #include<cstring>
 5 #include<cmath>
 6 #include<queue>
 7 using namespace std;
 8 const int maxn=500010;
 9 int n;
10 int ch[maxn][2]={},siz[maxn]={},dis[maxn]={},val[maxn]={};
11 int l[maxn]={},r[maxn]={},b[maxn]={},tot=0;;
12 int a[maxn]={};
13 inline void updata(int x){
14     siz[x]=siz[ch[x][0]]+siz[ch[x][1]]+1;
15 }
16 int merge(int x,int y){
17     if(x==0)return y;if(y==0)return x;
18     if(val[x]<val[y])swap(x,y);
19     ch[x][1]=merge(ch[x][1],y);
20     if(dis[ch[x][1]]>dis[ch[x][0]])swap(ch[x][1],ch[x][0]);
21     dis[x]=dis[ch[x][1]]+1;
22     updata(x);
23     return x;
24 }
25 int main(){
26     scanf("%d",&n);
27     for(int i=1;i<=n;i++){
28         scanf("%d",&a[i]);siz[i]=1;val[i]=a[i];
29         l[++tot]=i;r[tot]=i;b[tot]=i;
30         while(tot>1&&val[b[tot-1]]>val[b[tot]]){
31             b[tot-1]=merge(b[tot-1],b[tot]);
32             r[tot-1]=r[tot];
33             while(siz[b[tot-1]]>(r[tot-1]-l[tot-1]+2)/2)
34                 b[tot-1]=merge(ch[b[tot-1]][0],ch[b[tot-1]][1]);
35             --tot;
36         }
37     }
38     long long ans=0;
39     for(int i=1;i<=tot;i++){
40         for(int j=l[i];j<=r[i];j++){
41             ans+=abs(val[b[i]]-a[j]);
42         }
43     }printf("%lld
",ans);
44     return 0;
45 }
View Code

原文地址:https://www.cnblogs.com/137shoebills/p/8666193.html