今天学了左偏树,看了一天,一些细节还是不太明白。有点混乱。做题的时候也不是很明白方法。。。
先来介绍左偏树(见论文):
左偏树的特点及其应用 (还是不太理解左偏树的复杂度以及实现方法,例题题解也看不明白,需要再好好看看)
左偏树
#include<cstdio>
#include<iostream>
#include <algorithm>
#include <map>
#include <cmath>
using namespace std;
const int maxn=100005;
int n,m,a,b;
struct tree{
int l,r,v,dis,f;
}heap[maxn];
int merge(int a,int b){
if(a==0) return b;
if(b==0) return a;
if(heap[a].v<heap[b].v) swap(a,b);
heap[a].r=merge(heap[a].r,b);
heap[heap[a].r].f=a;
if(heap[heap[a].l].dis<heap[heap[a].r].dis) swap(heap[a].l,heap[a].r);
if(heap[a].r==0) heap[a].dis=0;
else heap[a].dis=heap[heap[a].r].dis+1;
return a;
}
int pop(int a){
int l=heap[a].l;
int r=heap[a].r;
heap[l].f=l;
heap[r].f=r;
heap[a].l=heap[a].r=heap[a].dis=0;
return merge(l,r);
}
int find(int a){
return heap[a].f==a?a:find(heap[a].f);
}
int main() {
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
#endif
while(cin>>n){
for(int i=1;i<=n;i++){
cin>>heap[i].v;
heap[i].l=heap[i].r=heap[i].dis=0;
heap[i].f=i;
}
cin>>m;
while(m--){
cin>>a>>b;
int fa=find(a),fb=find(b);
if(fa==fb)
cout<<"-1"<<endl;
else{
heap[fa].v/=2;
int u=pop(fa);
u=merge(u,fa);
heap[fb].v/=2;
int v=pop(fb);
v=merge(v,fb);
cout<<heap[merge(u,v)].v<<endl;
}
}
}
}
Description
![](http://img0.tuicool.com/MzuIv2U.jpg!web)
bzoj1367 [Baltic2004]sequence [左偏树]
[Baltic2004]sequence。。。初学左偏树 http://blog.csdn.net/mznanan/article/details/6084296 (1)对于求不下降序列 最后的做法就是:维护几段连续的序列,使它们的中位数不下降
然而转化到递增序列,我们只需要将每个数读进来的之后减去它的下标就可以了(a[i]=a[i]-i)[不明白这个技巧,先记着]
所以我们对于每一段已求好的序列,既要维护它的中位数,又要支持合并
因为我们合并的前提是:中位数(i)>中位数(i+1),那么对于合并后的i而言,中位数肯定是不升的
根据这个性质我们又可以用可并堆了,堆顶元素表示该序列中的中位数
当堆的元素个数*2>序列长度+1的时候就可以弹出堆顶
先考虑只要求 z1<=z2<=...<=zn :
两个特殊情况:
- t[1]<=t[2]<=...<=t[n] ,此时z[i] = t[i].
- t[1]>=t[2]>=...>=t[n] ,此时z[i]=x,x为序列t的中位数.
于是可以将原数列划分成m个区间,每一段的解为该区间的中位数。
实现:
假设已经求出了前k个数的最优解,被划分成了m个区间,每段区间的最优解为 w[i](w[1]<=w[2]<=...<=w[m]) ,现在考虑第k + 1个数,先将t[k + 1]单独看作一个区间,最优解为w[m+1],此时假如 w[m]>w[m+1] ,则合并区间m,m + 1,然后找出新区间的解(中位数),重复上述过程直到 w[m]<=w[m+1] .
如何维护中位数:当堆的大小大于区间长度的一半时删除堆顶元素,则堆中的元素一定是该区间内较小的一半元素,堆顶元素即为该区间的中位数。
这只是 z1<=z2<=...<=zn 的情况。。
然而要求递增只需要将原本的t[i]改成t[i] - i,再按照上述做法做就行了orz。
#include <cstdio>
#include <iostream>
#include <cstring>
#include <queue>
#include <cmath>
using namespace std;
const int maxn=100005;
//没有提交过,bzoj上找不到,所以不知道对不对,参考上面链接的代码
/*
v 键值,tl 左节点 tr 右节点 dis 距离 size 堆大小 root 堆节点
l 堆左区间 r 堆右区间
tot记录节点个数 cnt 记录堆个数
*/
int n,a[maxn];
int tot=0;
int v[maxn],tl[maxn],tr[maxn],dis[maxn],size[maxn];
int root[maxn];
int l[maxn],r[maxn];
int merge(int x,int y){
if(!x||!y) return x+y;
if(v[x]<v[y]) swap(x,y);
tr[x]=merge(tr[x],y);
size[x]=size[tr[x]]+size[tl[x]]+1;
if(dis[tr[x]]>dis[tl[x]]) swap(tl[x],tr[x]);
dis[x]=dis[tr[x]]+1;
return x;
}
int newNode(int x){
v[++tot]=x;
size[tot]=1;
tl[tot]=tr[tot]=dis[tot]=0;
return tot;
}
void pop(int &x){
x=merge(tl[x],tr[x]);
}
int main() {
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
#endif
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];a[i]-=i;
}
int cnt=0;
for(int i=1;i<=n;i++){
cnt++;
root[cnt]=newNode(a[i]);
l[cnt]=r[cnt]=i;
while(cnt>1&&v[root[cnt]]<v[root[cnt-1]]){
cnt--;
root[cnt]=merge(root[cnt],root[cnt+1]);
r[cnt]=r[cnt+1];
while(size[root[cnt]]*2>r[cnt]-l[cnt]+2) //维护中位数,堆大小>(区间长度+1)/2
pop(root[cnt]);
}
}
int ans=0;
for(int i=1;i<=cnt;i++){
for(int j=l[i];j<=r[i];j++){
ans+=abs(v[root[i]]-a[j]);
}
}
cout<<ans<<endl;
}