这道题哪里都找不到。

[问题描述]

给定一个整数序列a1, a2, … , an,求一个不下降序列b1 ≤ b2 ≤ … ≤ bn,使得数列{ai}和{bi}的各项之差的绝对值之和 |a1 - b1| + |a2 - b2| + … + |an - bn| 最小。

[数据规模] 1 ≤ n ≤ 106, 0 ≤ ai ≤ 2*109

    这道题很有趣,值得一做。

    我们对于这种题目,要有有效的思维方式。

    ①:考虑最终的答案数列B,它能够看成是很多段相同的数段连接而成的,构成一个不下降的序列。(比如说:111 2 33333 4444 5)

    ②:猜想如何获得B序列,根据B序列的性质(①),我们猜测某两个连续的区间最优的答案(规定这两段序列分别是同一个值)可以快速合并。下面来"伪"证一下猜想。两段在位置上是相邻的,设左半边的最优值为U,右半边为V,若U≤V,则两段答案可以共存。不然,则需要继续探讨:(这时两段的最优解是不能共存的,因为这不能构成一个不下降的B数列),因此需要找到一个新的答案,为B[1~n(并上)n+1~m](其中原B[1~n]为U,原B[n+1~m]为V),先挖掘一下性质:1)B[n]<U,2)B[n+1]>V。这两点的正确性是显然的。假设我们现在已经知道B[n]应该是哪个数,那么B[n-1]我是希望尽量大的(因为B[n-1]≤B[n]<U,U是左半段的最优解,贪心的策略就是让B[n-1]的值尽量靠近U,所以B[n-1]=B[n]才是最理想的,继续考虑B[n-2]的值,B[n-2]=B[n-1],B[n-3]=B[n-2]……,最终得出左半段是一个值,同理右半段也是一个值,那左右是否就是同一个值呢?(不是的话这题还能怎么做呢?)假设新的答案为U1和V1,U1≤V1,若U1<V1,则贪心的,我让U1等于V1,则答案会更优,所以U1必定等于V1。太棒了,我现在用一堆弱弱的证明解决了大部分问题,还要考虑的就是U1的求法。

    ③:由于是同一个值,是否直接用中位数就好了?让人欣喜若狂的是,这是显然的(我都懒得证了)。为了编程方便,用排序后的A数组下标为floor(M/2)也是对的。

    ④:可以得到这样一个算法:

        from 1 to m

        新建一个答案区间i~i,值为A[i]

        if(A[i]小于它左边区间的答案)

            合并两个区间

            获取"中位数"作为新区间的答案

        endif

        else    continue

    ⑤:如何合并?考虑平衡树?太麻烦了,考虑用主席树或可并堆吧。主席树就是求区间第K大,几乎模板;而可并堆的做法复杂一些

    好感人,至此这道题应该就没有什么疑点了吧。

    我觉得做这道题要靠运气,很多地方要先猜到再证明,没有绝对的实力是很痛苦的,希望在不久的将来我能有绝对的实力。

    这是黄源河大牛的题解。(简直没得比)

[初步分析]

我们先来看看两个最特殊的情况:

  1. a[1]≤a[2]≤…≤a[n],在这种情况下,显然最优解为b[i]=a[i];
  2. a[1]≥a[2]≥…≥a[n],这时,最优解为b[i]=x,其中x是数列a的中位数。

于是我们可以初步建立起这样一个思路:

把1…n划分成m个区间:[q[1],q[2]-1],[q[2],q[3]-1],……,[q[m],q[m+1]-1]。每个区间对应一个解,b[q[i]] = b[q[i]+1] = … = b[q[i+1]-1] = w[i],其中w[i] 为a[q[i]], a[q[i]+1], ... , a[q[i+1]-1] 的中位数。

显然,在上面第一种情况下m=n,q[i]=i;在第二种情况下m=1,q[1]=1。

这样的想法究竟对不对呢?应该怎样实现?

若某序列前半部分a[1], a[2], … , a[n] 的最优解为 (u,u,…,u),后半部分a[n+1], a[n+2], ... , a[m] 的最优解为 (v,v,…,v),那么整个序列的最优解是什么呢?若u≤v,显然整个序列的最优解为 (u,u,…,u,v,v,…,v)。否则,设整个序列的最优解为 (b[1],b[2],…,b[m]),则显然b[n]≤u(否则我们把前半部分的解 (b[1],b[2],…,b[n]) 改为 (u,u,…,u),由题设知整个序列的解不会变坏),同理b[n+1]≥v。接下来,我们将看到下面这个事实:

对于任意一个序列a[1],a[2],…,a[n],如果最优解为 (u,u,…,u),那么在满足u≤u′≤b[1] 或b[n]≤u′≤u的情况下,(b[1],b[2],…,b[n]) 不会比 (u′,u′,…,u′) 更优。

我们用归纳法证明u≤u′≤b[1] 的情况,b[n]≤u′≤u的情况可以类似证明。

当n=1时,u=a[1],命题显然成立。

当n>1时,假设对于任意长度小于n的序列命题都成立,现在证明对于长度为n的序列命题也成立。首先把 (b[1], b[2], … b[n]) 改为 (b[1], b[1], … b[1]),这一改动将不会导致解变坏,因为如果解变坏了,由归纳假设可知a[2],a[3],…,a[n]的中位数w>u,这样的话,最优解就应该为(u,u,…,u,w,w,…,w),矛盾。然后我们再把(b[1],b[1],…,b[1])改为 (u′,u′,…,u′),由于| a[1]- x | + | a[2]- x | + … + | a[n]- x | 的几何意义为数轴上点x到点a[1], a[2], … a[n]的距离之和,且u≤u′≤b[1],显然点u′到各点的距离之和不会比点b[1]到各点的距离之和大,也就是说,(b[1],b[1],…,b[n]) 不会比 (v,v,…,v) 更优。(证毕)

再回到之前的论述,由于b[n]≤u,作为上述事实的结论,我们可以得知,将(b[1],b[2],…,b[n])改为(b[n],b[n],…,b[n]),再将(b[n+1],b[n+2],…,b[m])改为(b[n+1],b[n+1],…,b[n+1]),并不会使解变坏。也就是说,整个序列的最优解为(b[n],b[n],…,b[n],b[n+1],b[n+1],…,b[n+1])。再考虑一下该解的几何意义,设整个序列的中位数为w,则显然令b[n]=b[n+1]=w将得到整个序列的最优解,即最优解为 (w,w,…,w)。

分析到这里,我们一开始的想法已经有了理论依据,算法也不难构思了。

[算法描述]

延续我们一开始的思路,假设我们已经找到前k个数a[1], a[2], … , a[k] (k<n) 的最优解,得到m个区间组成的队列,对应的解为 (w[1],w[2],…,w[m]),现在要加入a[k+1],并求出前k+1个数的最优解。首先我们把a[k+1]作为一个新区间直接加入队尾,令w[m+1]=a[k+1],然后不断检查队尾两个区间的解w[m]和w[m+1],如果w[m]>w[m+1],我们需要将最后两个区间合并,并找出新区间的最优解(也就是序列a中,下标在这个新区间内的各项的中位数)。重复这个合并过程,直至w[1]≤w[2]≤…≤w[m]时结束,然后继续处理下一个数。

这个算法的正确性前面已经论证过了,现在我们需要考虑一下数据结构的选取。算法中涉及到以下两种操作:合并两个有序集以及查询某个有序集内的中位数。能较高效地支持这两种操作的数据结构有不少,一个比较明显的例子是二叉检索树(BST),它的询问操作复杂度是O(logn),但合并操作不甚理想,采用启发式合并,总时间复杂度为O(nlog2n)。

有没有更好的选择呢?通过进一步分析,我们发现,只有当某一区间内的中位数比后一区间内的中位数大时,合并操作才会发生,也就是说,任一区间与后面的区间合并后,该区间内的中位数不会变大。于是我们可以用最大堆来维护每个区间内的中位数,当堆中的元素大于该区间内元素的一半时,删除堆顶元素,这样堆中的元素始终为区间内较小的一半元素,堆顶元素即为该区间内的中位数。考虑到我们必须高效地完成合并操作,左偏树是一个理想的选择。左偏树的询问操作时间复杂度为O(1),删除和合并操作时间复杂度都是O(logn),而询问操作和合并操作少于n次,删除操作不超过n/2次(因为删除操作只会在合并两个元素个数为奇数的堆时发生),因此用左偏树实现,可以把算法的时间复杂度降为O(nlogn)。

[小结]

这道题的解题过程对我们颇有启示。在应用左偏树解题时,我们往往会觉得题目无从下手,甚至与左偏树毫无关系,但只要我们对题目深入分析,加以适当的转化,问题终究会迎刃而解。这需要我们具有敏捷的思维以及良好的题感。

用左偏树解本题,相比较于前面BST的解法,时间复杂度和编程复杂度更低,这使我们不得不感叹于左偏树的神奇威力。这不是说左偏树就一定是最好的解法,就本题来说,解法有很多种,光是可并堆的解法,就可以用多种数据结构来实现,但左偏树相对于它们,还是有一定的优势的,这将在下一章详细讨论。

 #include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
using namespace std;
const int maxn=;
int B[maxn];
int q[maxn],l[maxn],r[maxn],num[maxn],dep[maxn],tot;
int ch[maxn][],key[maxn],n;
int Merge(int x,int y){
if(!x||!y)return x|y;
if(key[x]<key[y])swap(x,y);
ch[x][]=Merge(ch[x][],y);
if(dep[ch[x][]]>dep[ch[x][]])
swap(ch[x][],ch[x][]);
dep[x]=dep[ch[x][]]+;
return x;
} void Delete(int i){
q[i]=Merge(ch[q[i]][],ch[q[i]][]);
} int main(){
dep[]=-;
scanf("%d",&n);
for(int i=;i<=n;i++)
scanf("%d",&key[i]);
for(int i=;i<=n;i++){
q[++tot]=i;l[tot]=r[tot]=i;num[tot]=;
while(tot!=&&key[q[tot]]<key[q[tot-]]){
q[tot-]=Merge(q[tot],q[tot-]);
num[tot-]+=num[tot];
r[tot-]=r[tot];tot--;
int sum=(r[tot]-l[tot]+)/;
while(num[tot]>sum){
Delete(tot);
num[tot]-=;
}
}
} for(int i=;i<=tot;i++)
for(int j=l[i];j<=r[i];j++)
B[j]=key[q[i]]; int ans=;
for(int i=;i<=n;i++)
ans+=abs(key[i]-B[i]);
printf("%d\n",ans);
return ;
}

黄源河《左偏树的应用》——数字序列(Baltic 2004)的更多相关文章

  1. 洛谷P4331 [BOI2004] Sequence 数字序列 [左偏树]

    题目传送门 数字序列 题目描述 给定一个整数序列 a1​,a2​,⋅⋅⋅,an​ ,求出一个递增序列 b1​<b2​<⋅⋅⋅<bn​ ,使得序列 ai​ 和 bi​ 的各项之差的绝对 ...

  2. [BOI2004]Sequence 数字序列(左偏树)

    PS:参考了黄源河的论文<左偏树的特点及其应用> 题目描述:给定一个整数序列\(a_1, a_2, - , a_n\),求一个递增序列\(b_1 < b_2 < - < ...

  3. 2021.08.01 P4311 数字序列(左偏树)

    2021.08.01 P4311 数字序列(左偏树) [P4331 BalticOI 2004]Sequence 数字序列 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 重点: 1 ...

  4. Luogu P4331 [BOI2004]Sequence 数字序列 (左偏树论文题)

    清晰明了%%% Fairycastle的博客 个人习惯把size什么的存在左偏树结点内,这样在外面好写,在里面就是模板(只用修改update). 可以对比一下代码(好像也差不多-) MY CODE # ...

  5. 洛谷$P4331\ [BOI2004]\ Sequence$ 数字序列 左偏树

    正解:左偏树 解题报告: 传送门$QwQ$ 开始看到的时候$jio$得长得很像之前做的一个$dp$,,, 但是$dp$那题是说不严格这里是严格? 不难想到我们可以让$a_{i},b_{i}$同时减去$ ...

  6. POJ3666-Making the Grade(左偏树 or DP)

    左偏树 炒鸡棒的论文<左偏树的特点及其应用> 虽然题目要求比论文多了一个条件,但是……只需要求非递减就可以AC……数据好弱…… 虽然还没想明白为什么,但是应该觉得应该是这样——求非递减用大 ...

  7. HDU 1512 Monkey King(左偏树+并查集)

    [题目链接] http://acm.hdu.edu.cn/showproblem.php?pid=1512 [题目大意] 现在有 一群互不认识的猴子,每个猴子有一个能力值,每次选择两个猴子,挑出他们所 ...

  8. HDU3031 To Be Or Not To Be 左偏树 可并堆

    欢迎访问~原文出处——博客园-zhouzhendong 去博客园看该题解 题目传送门 - HDU3031 题意概括 喜羊羊和灰太狼要比赛. 有R次比赛. 对于每次比赛,首先输入n,m,n表示喜羊羊和灰 ...

  9. HDU5818 Joint Stacks 左偏树,可并堆

    欢迎访问~原文出处——博客园-zhouzhendong 去博客园看该题解 题目传送门 - HDU5818 题意概括 有两个栈,有3种操作. 第一种是往其中一个栈加入一个数: 第二种是取出其中一个栈的顶 ...

随机推荐

  1. svn 设置钩子将代码同步到web目录下面

    首先:确定思路: 要在SVN服务中,找到仓库文件夹的位置,在相应的项目中找到hooks文件夹.在该文件中添加一个post-commit文件: 当有commit动作发生时(提交到SVN服务是就会执行这个 ...

  2. 【转】纯 CSS 实现高度与宽度成比例的效果

    先来演示页面:Demo; 转的内容: 最近在做一个产品列表页面,布局如右图所示.页面中有若干个 item,其中每个 item 都向左浮动,并包含在自适应浏览器窗口宽度的父元素中. item 元素的 C ...

  3. mysql隐藏文件一定要删除彻底

    之前部署自己的服务器机器的时候 机器的mysql密码是不知道的.彻底删除了软件之后还是解决不了问题,而且我把MYSQL在C盘的隐藏文件也给删除了.但是还是不行 最后我偶然发现一个方法去找隐藏问题.我之 ...

  4. SQL某个字段在原内容上增加固定内容或replace查找替换内容

    今天正好遇到一个SQL小问题,特做备注 在原有的表中数据如pic 在不动原内容的基础上增加../路径,但不能修改原数据值 原数据 SQL: pic字段 需要增加'../'的内容 update Bmps ...

  5. Asp.Net 注册 邮箱激活

    数据库 表的设计 State为用户状态  0为禁用  1为可用  默认为0,下面有个UserGUID,这个字段将来用于激活账户 首先你要写一个表单,验证码神马的,这个我就不写了..直接写处理的 代码在 ...

  6. Strut2 采用token机制防御CSRF同时也可以防止表单重复提交

    一 未配置Struts2 token的情况下测试 1.从表单提交数据,可以从下图看出,快速点击保存按钮,请求提交了两次 2.检查post提交的数据中未含有token参数 3.查看数据列表,有重复数据 ...

  7. 【BZOJ1146】【树链剖分+平衡树】网络管理Network

    Description M 公司是一个非常庞大的跨国公司,在许多国家都设有它的下属分支机构或部门.为了让分布在世界各地的N个部门之间协同工作,公司搭建了一个连接整个公司的通 信网络.该网络的结构由N个 ...

  8. cmd 命令行下复制、粘贴的快捷键

    1.单击左下角“开始”菜单,选择“运行”,输入“cmd”. 2.在弹出的cmd窗口的标题栏上点击“右键”,选择“属性”. 3.在弹出的对话框中选择“选项”这个选项卡,在“编辑选项”区域中勾选“快速编辑 ...

  9. Cocos2dx开发(3)——Cocos2dx打包成APK,ANT环境搭建

    前面cocos2dx的运行环境(Android SDK,JDK,),最后Cocos2dx的APK的打包环境,最运行环境上再加ANT环境就好了 1.ANT下载配置 官网下载:http://ant.apa ...

  10. 常见ORACLE错误,及解决方案(遇则即时更新)

    1.当登陆时提示“ORA-03113:通信通道的文件结束”时:        解决方案:                     需在X:\oraclexe\app\oracle\product\10 ...