洛谷P4331 [BOI2004] Sequence 数字序列 [左偏树]
数字序列
题目描述
给定一个整数序列 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 ,若有多种方案,只需输出其中一种。
输入输出样例
说明
【数据范围】
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 数字序列 [左偏树]的更多相关文章
- 洛谷$P4331\ [BOI2004]\ Sequence$ 数字序列 左偏树
正解:左偏树 解题报告: 传送门$QwQ$ 开始看到的时候$jio$得长得很像之前做的一个$dp$,,, 但是$dp$那题是说不严格这里是严格? 不难想到我们可以让$a_{i},b_{i}$同时减去$ ...
- Luogu P4331 [BOI2004]Sequence 数字序列 (左偏树论文题)
清晰明了%%% Fairycastle的博客 个人习惯把size什么的存在左偏树结点内,这样在外面好写,在里面就是模板(只用修改update). 可以对比一下代码(好像也差不多-) MY CODE # ...
- 洛谷P4331 [BOI2004]Sequence 数字序列(左偏树)
传送门 感觉……不是很看得懂题解在说什么? 我们先把原数列$a_i-=i$,那么本来要求递增序列,现在只需要求一个非严格递增的就行了(可以看做最后每个$b_i+=i$,那么非严格递增会变为递增) 如果 ...
- 洛谷P4331[BOI2004] sequence
博客复活? 这个题很模板啊.随便上个左偏树.之前第一遍写对了.然后今天翻出来又写了一遍发现了一个奇奇怪怪的问题. 对比如下 上面的是AC 下面的WA 真的是一个很蠢的问题...你TM堆顶都弹出来了,堆 ...
- 洛谷.3273.[SCOI2011]棘手的操作(左偏树)
题目链接 还是80分,不是很懂. /* 七个操作(用左偏树)(t2表示第二棵子树): 1.合并:直接合并(需要将一个t2中原有的根节点删掉) 2.单点加:把这个点从它的堆里删了,加了再插入回去(有负数 ...
- 洛谷P3273 [SCOI2011] 棘手的操作 [左偏树]
题目传送门 棘手的操作 题目描述 有N个节点,标号从1到N,这N个节点一开始相互不连通.第i个节点的初始权值为a[i],接下来有如下一些操作: U x y: 加一条边,连接第x个节点和第y个节点 A1 ...
- 洛谷P3066 [USACO12DEC] 逃跑的Barn [左偏树]
题目传送门 逃跑的Barn 题目描述 It's milking time at Farmer John's farm, but the cows have all run away! Farmer J ...
- 模板 可并堆【洛谷P3377】 【模板】左偏树(可并堆)
P3377 [模板]左偏树(可并堆) 如题,一开始有N个小根堆,每个堆包含且仅包含一个数.接下来需要支持两种操作: 操作1: 1 x y 将第x个数和第y个数所在的小根堆合并(若第x或第y个数已经被删 ...
- P4331 [BOI2004]Sequence 数字序列 (左偏树)
[题目链接] https://www.luogu.org/problemnew/show/P4331 题目描述 给定一个整数序列\(a_1, a_2, ··· , a_n,\)求出一个递增序列\(b_ ...
随机推荐
- 51Nod 1347 旋转字符串 | 规律
Input示例 aa ab Output示例 YES NO 规律:abcabc 只需判断原始字符串 #include <bits/stdc++.h> using namespace std ...
- 51nod 1284 2 3 5 7的倍数 | 容斥原理
用容斥原理求出不满足条件的个数cnt,然后用n-cnt就得到答案了. 这里不满条件的数就是能整除2,3,5,7这些数的集合并集.要计算几个集合并集的大小,我们要先将所有单个集合的大小计算出来,然后减去 ...
- 「模板」 FHQ_Treap 区间翻转
「模板」 FHQ_Treap 区间翻转 没有旋转的 Treap 实现区间操作的功能,很好理解,也很好写,只是速度不算太快. 对于要翻转的区间,把整棵 Treap(存有区间 \([1,n]\) 的信息) ...
- PowerDesigner16 设置导出sql文件的编码
一: 导出数据库结构sql脚本 Database -> Generate Database 在弹窗中选择Format选项卡,修改Encoding,选择自己需要的编码格式. 二:比较数据库差异 ...
- 初识Webx 1
Webx是一套基于Java Servlet API的通用Web框架.它在Alibaba集团内部被广泛使用.从2010年底,向社会开放源码. Webx框架是一个稳定.强大的Web框架.建立在Spring ...
- Maven-Optional Dependencies & Dependency Exclusion
本文讨论可选依赖和排除依赖. 帮助用户理解它们是什么, 如何使用, 它们如何工作, 以及什么时候使用它们最合适. 本文也将解释为什么排除是基于单个依赖的, 而非POM级别的. Optional De ...
- CAS(硬件CPU同步原语)
CAS有3个操作数.内存值V,旧的预约值A,要修改后的新值B.当且仅当预期值A和预期值V相同时,将内存值V修改为新值B.当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做. 应用1. ...
- EntitySpace 常用语句
EntitySpace 这个是很早期的ORM框架,最近发现这个破解的也都不能用了.有谁知道能用的,联系我. 1. where带几个条件的 query.Where(query.ProductTempSt ...
- 在非ARC工程中使用ARC库
选中工程->TARGETS->相应的target然后选中右侧的“Build Phases”,向下就找到“Compile Sources”了.为对应的库文件添加:-fobjc-arc参数即可 ...
- python学习笔记(五)数值类型和类型转换
Python中的数值类型有: 整型,如2,520 浮点型,如3.14159,1.5e10 布尔类型 True和False e记法: e记法即对应数学中的科学记数法 >>> 1.5e1 ...