数据结构----左偏树

今天学了左偏树,看了一天,一些细节还是不太明白。有点混乱。做题的时候也不是很明白方法。。。


先来介绍左偏树(见论文):

图解数据结构(9)——左偏树

左偏树的特点及其应用    (还是不太理解左偏树的复杂度以及实现方法,例题题解也看不明白,需要再好好看看)

左偏树


见例题:


判断是否认识,并查集。。。。因为要用到合并,而且输出最强壮值,二叉堆不能合并,所有要用到能够合并的数据结构----左偏树。这题能够好好理解左偏树的实现方法。
#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;
			}
		}
	}
    
}


例2就是论文《左偏树的特点及其应用》里面的例题,那个结论的论证过程还是看不明白。。。对于别人的代码也是懵懂的阶段。。。。本来打算做多几道左偏树,但是要花费的时间太长了,而自己有那么蠢~~QAQ。。。这题在bzoj上找不到了,自己照着别人写的代码也不知道对不对。。

Description


至于方法见论文以及下面链接:

http://www.tuicool.com/articles/qInyEru

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的时候就可以弹出堆顶

   (2)

先考虑只要求 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;
}                        



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值