题目传送门

数字序列

题目描述

给定一个整数序列 a1​,a2​,⋅⋅⋅,an​ ,求出一个递增序列 b1​<b2​<⋅⋅⋅<bn​ ,使得序列 ai​ 和 bi​ 的各项之差的绝对值之和 ∣a1​−b1​∣+∣a2​−b2​∣+⋅⋅⋅+∣an​−bn​∣ 最小。

输入输出格式

输入格式:

第一行为数字 n (1≤n≤10^6) ,接下来一行共有 n 个数字,表示序列 a_i (0≤a_i≤2×10^9) 。

输出格式:

第一行输出最小的绝对值之和。

第二行输出序列 bi​ ,若有多种方案,只需输出其中一种。

输入输出样例

输入样例#1: 复制

5
2 5 46 12 1
输出样例#1: 复制

47
2 5 11 12 13

说明

【数据范围】

40%的数据 n≤5000

60%的数据 n≤300000

100%的数据 n≤1000000 , 0≤a_i≤2×10^9

题目来源: Baltic OI 2004 Day1, Sequence


  分析:

  不得不说,一道左偏树神仙题。一开始看到这道题,基本上想到的都是数学方法,或者是想到线段树之类的数据结构。后面才知道是左偏树,而且是个论文题,思路实在巧妙。

  以下直接引用自黄源河的论文,这里蒟蒻就只放代码:

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

  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+1],我们需要将最后两个区间合并,并找出新区间的 最优解(也就是序列 a 中,下标在这个新区间内的各项的中位数)。重复这个合 并过程,直至 w[1]≤w[2]≤…≤w[m]时结束,然后继续处理下一个数。

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

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

  

  Code:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e6+;
int n,top;ll b[N],ans;
struct Node{
int l,r,top,size;ll val;
}stac[N];
struct Leftist{
int fa[N],ch[N][],dis[N];
ll val[N];
int merge(int x,int y)
{
if(x==||y==)return x+y;
if(val[x]<val[y])swap(x,y);
ch[x][]=merge(ch[x][],y);
fa[ch[x][]]=x;
if(dis[ch[x][]]>dis[ch[x][]])
swap(ch[x][],ch[x][]);
dis[x]=dis[ch[x][]]+;
return x;
}
int del(int x)
{
fa[ch[x][]]=fa[ch[x][]]=;
int ret=merge(ch[x][],ch[x][]);
ch[x][]=ch[x][]=;
return ret;
}
}T;
inline ll read()
{
char ch=getchar();ll num=;bool flag=false;
while(ch<''||ch>''){if(ch=='-')flag=true;ch=getchar();}
while(ch>=''&&ch<=''){num=num*+ch-'';ch=getchar();}
return flag?-num:num;
}
void work()
{
stac[++top]=(Node){,,,,T.val[]};
for(int i=;i<=n;i++){
int l=stac[top].r+;
stac[++top]=(Node){l,i,i,,T.val[i]};
while(top!=&&stac[top].val<stac[top-].val){
--top;
stac[top].top=T.merge(stac[top].top,stac[top+].top);
stac[top].size=stac[top].size+stac[top+].size;
stac[top].r=stac[top+].r;
while(stac[top].size>(stac[top].r-stac[top].l+)/){
--stac[top].size;stac[top].top=T.del(stac[top].top);}
stac[top].val=T.val[stac[top].top];
}
}
int cnt=;
for(int i=;i<=n;i++){
while(i>stac[cnt].r)cnt++;
ans+=abs(T.val[i]-stac[cnt].val);
b[i]=stac[cnt].val+i;}
printf("%lld\n",ans);
for(int i=;i<=n;i++)
printf("%lld ",b[i]);
}
int main()
{
n=read();
for(int i=;i<=n;i++){
T.val[i]=read();
T.val[i]-=i;}
work();return ;
}

洛谷P4331 [BOI2004] Sequence 数字序列 [左偏树]的更多相关文章

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

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

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

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

  3. 洛谷P4331 [BOI2004]Sequence 数字序列(左偏树)

    传送门 感觉……不是很看得懂题解在说什么? 我们先把原数列$a_i-=i$,那么本来要求递增序列,现在只需要求一个非严格递增的就行了(可以看做最后每个$b_i+=i$,那么非严格递增会变为递增) 如果 ...

  4. 洛谷P4331[BOI2004] sequence

    博客复活? 这个题很模板啊.随便上个左偏树.之前第一遍写对了.然后今天翻出来又写了一遍发现了一个奇奇怪怪的问题. 对比如下 上面的是AC 下面的WA 真的是一个很蠢的问题...你TM堆顶都弹出来了,堆 ...

  5. 洛谷.3273.[SCOI2011]棘手的操作(左偏树)

    题目链接 还是80分,不是很懂. /* 七个操作(用左偏树)(t2表示第二棵子树): 1.合并:直接合并(需要将一个t2中原有的根节点删掉) 2.单点加:把这个点从它的堆里删了,加了再插入回去(有负数 ...

  6. 洛谷P3273 [SCOI2011] 棘手的操作 [左偏树]

    题目传送门 棘手的操作 题目描述 有N个节点,标号从1到N,这N个节点一开始相互不连通.第i个节点的初始权值为a[i],接下来有如下一些操作: U x y: 加一条边,连接第x个节点和第y个节点 A1 ...

  7. 洛谷P3066 [USACO12DEC] 逃跑的Barn [左偏树]

    题目传送门 逃跑的Barn 题目描述 It's milking time at Farmer John's farm, but the cows have all run away! Farmer J ...

  8. 模板 可并堆【洛谷P3377】 【模板】左偏树(可并堆)

    P3377 [模板]左偏树(可并堆) 如题,一开始有N个小根堆,每个堆包含且仅包含一个数.接下来需要支持两种操作: 操作1: 1 x y 将第x个数和第y个数所在的小根堆合并(若第x或第y个数已经被删 ...

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

    [题目链接] https://www.luogu.org/problemnew/show/P4331 题目描述 给定一个整数序列\(a_1, a_2, ··· , a_n,\)求出一个递增序列\(b_ ...

随机推荐

  1. Git新手上路,让你快速掌握Git的基本使用

    github是一个基于git的代码托管平台,付费用户可以建私人仓库,我们一般的免费用户只能使用公共仓库,也就是代码要公开.这对于一般人来说公共仓库就已经足够了. 1.注册账户以及创建仓库     要想 ...

  2. 51Nod 1003 阶乘后面0的数量 | 思维

    题意:n的阶乘后面0的个数,如果直接算出阶乘再数0的数量一定会超时的. 因为10=2*5,所以求出5贡献的次数就行. #include "bits/stdc++.h" using ...

  3. 【uva12232/hdu3461】带权并查集维护异或值

    题意: 对于n个数a[0]~a[n-1],但你不知道它们的值,通过逐步提供给你的信息,你的任务是根据这些信息回答问题: I P V :告诉你a[P] = V I P Q V:告诉你a[P] XOR a ...

  4. Spring Session加Redis(山东数漫江湖)

    session是一个非常常见的概念.session的作用是为了辅助http协议,因为http是本身是一个无状态协议.为了记录用户的状态,session机制就应运而生了.同时session也是一个非常老 ...

  5. js中三种定义变量 const, var, let 的区别

    js中三种定义变量的方式const, var, let的区别 1.const定义的变量不可以修改,而且必须初始化. 1 const b = 2;//正确 2 // const b;//错误,必须初始化 ...

  6. hdu 2717 Catch That Cow(广搜bfs)

    题目链接:http://i.cnblogs.com/EditPosts.aspx?opt=1 Catch That Cow Time Limit: 5000/2000 MS (Java/Others) ...

  7. python基础===* 解包,格式化输出和print的一点知识

    python3中的特性: >>> name = "botoo" >>> print(f"my name is {name}" ...

  8. Linux内核通知链机制的原理及实现【转】

    转自:http://www.cnblogs.com/armlinux/archive/2011/11/11/2396781.html 一.概念: 大多数内核子系统都是相互独立的,因此某个子系统可能对其 ...

  9. python的时间和日期--time、datetime应用

    time >>> import time >>> time.localtime() #以time.struct_time类型,打印本地时间 time.struct_ ...

  10. python爬虫模块之数据存储模块

    数据存储模块的话,目前我这用的比较多的是存储到mysql,所以下面的这个例子也是保存到mysql,用到了ORM映射的SQLAlchemy ,(ORM:Object-Relational Mapping ...