这道题哪里都找不到。

[问题描述]

给定一个整数序列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. 安装SQL Server2008时 检测时有“重启计算机”失败

    第一种解决方案: 在学校的时候 遇到这种问题的解决办法是: 卸载VS,先安装SQL Server 2008 再安装VS 就行了: 第二种解决方案: 如果已经安装过VS,在安装SQL Server200 ...

  2. 怎样在thinkphp里面执行原生的sql语句

    $Model = new Model(); $sql = "select * from `order`"; $voList = $Model->query($sql); 只是 ...

  3. ASP.NET 导入EXCEL文档

    鉴于教务一般都是手动输入学生信息,在未了解本校数据库的客观情况之下,我们准备设计一个导入excel文档中学生信息如数据库的功能.结合网上各类大牛的综合版本出炉.. 首先具体的实现思想如下: 1.先使用 ...

  4. JavaScript HTML DOM - 改变CSS

    JavaScript HTML DOM - 改变CSS HTML DOM 允许 JavaScript 改变 HTML 元素的样式. 改变 HTML 样式 如需改变 HTML 元素的样式,请使用这个语法 ...

  5. nginx插件ngx_lua

    ngx_lua是淘宝的维护的产品,真心不错.配置文件包含可以做很多事情的lua脚本. 公司有个产品对注册的广告盒子进行反向代理,这样可以在盒子上做很多事情:和服务器通信,远程控制盒子等等.nginx反 ...

  6. Java发邮件:Java Mail与Apache Mail

    作者:Vinkn 来自http://www.cnblogs.com/Vinkn/ 一.邮件简介 一封邮件由很多信息构成,主要的信息如下,其他的暂时不考虑,例如抄送等: 1.收件人:收件人的邮箱地址,例 ...

  7. java中如何操作数据库(增删改查)

    EntityManager 是用来对实体Bean 进行操作的辅助类.他可以用来产生/删除持久化的实体Bean,通过主键查找实体bean,也可以通过EJB3 QL 语言查找满足条件的实体Bean.实体B ...

  8. php学习之基础语法

    这些语法都是在学习视频的过程中整理出来的,有些很简单的语法可能就没有整理了,只是记录了自己看来比较重要的语法内容.   1.变量使用 $ 声明 ,变量区分大小写   变量的类型:      4种标量类 ...

  9. 知识库总结mysql常用cmd命令

    打开命令目录 打开D盘mysql目录 d: cd D:\Ampps\mysql\bin 常用操作 将mysql目录下bin目录中的mysql.exe放到C:\WINDOWS下,可以执行以下命令 连接: ...

  10. WCF返回JSON的详细配置

    开发环境:VS2008,c# 1.新建个WCF服务网站 文件-新建-网站-WCF服务 2,运行一下,提示配置WEB.CONFIG,点击确认. 3,打开web.config增加如下节点: <ser ...