zoukankan      html  css  js  c++  java
  • 笔记——左偏树

    作用

    支持删除,合并,查询最大或最小值(还可以查询中位数哦)

    单次时间复杂度(O(log n))

    基本概念

    • 堆:二叉树

    • 空节点: 无左儿子的节点

    • 大根堆: 具有每个节点都满足该节点的权值大于等于她的儿子节点的堆

    • 小根堆: 与大根堆 相反的堆

    • 节点的距离: 走右儿子直到空节点为止浏览过的节点数

    • 左偏树: 左儿子的距离大于等于右儿子距离的堆

    性质

    • 是一个大(小)跟堆

    • 左儿子的距离大于等于右儿子距离

    • 节点的距离 等于该节点右儿子的距离 + 1(太水 不解释)

    • 一个有n个节点的左偏树她的(root)的距离不超过为(log(n+1)-1)

    由最后一个性质 保证一次到堆底的时间复杂度为(log)

    实现

    (merge)

    只需要学会(merge) 其她就很简单了

    (merge)就是将两个堆合并

    inline int merge(int u,int v)
    	{
    		if(!u||!v) return u | v;
         	至少有一个是0 返回另一个 等效于u+v
    		if(heap[u].val > heap[v].val)
    			swap(u,v);
          
    		heap[u].r = merge(heap[u].r,v);
          合并
    		if(dis[heap[u].l] < dis[heap[u].r])
    			swap(heap[u].l,heap[u].r);
          满足性质2
    		dis[u] = dis[heap[u].r] + 1;
          算距离(性质3)
    		return u;
    	}
    

    好了其实现在你已经可以(A)这道题了

    (delete)

    删除一个节点 等价于 合并这个节点的左右子树

    查询最大最小值

    每一颗左偏树 只能维护最大最小值中的一个

    由性质1得 堆的最大最小值为根的权值

    并查集

    第x个数和第y个数所在的小根堆合并

    这句活用并查集实现

    注意要路径压缩

    (Code)

    板子题

    #include <cstdio>
    #include <iostream>
    #define reg register int
    using namespace std; 
    const int MAXN = 100005;
    int n,m;
    namespace leftist_tree
    {
    	struct node {int l,r,val;};
    	node heap[MAXN];
    	bool exist[MAXN];
    	int dis[MAXN],rt[MAXN];
    	inline void init()
    	{
    		for(reg i = 1;i <= n;i++)
    			scanf("%d",&heap[i].val),rt[i] = i;
    	}
    	inline int merge(int u,int v)
    	{
    		if(!u||!v) return u | v;
    		if(heap[u].val > heap[v].val)
    			swap(u,v);
    		heap[u].r = merge(heap[u].r,v);
    		if(dis[heap[u].l] < dis[heap[u].r])
    			swap(heap[u].l,heap[u].r);
    		dis[u] = dis[heap[u].r] + 1;
    		return u;
    	}
    	inline int find_set(int x)
    	{
    		if(rt[x] == x) return x;
    		return rt[x] = find_set(rt[x]);
    	}
    }
    using namespace leftist_tree; 
    int main()
    {
    	scanf("%d%d",&n,&m);
    	leftist_tree::init();
    	while(m--)
    	{
    		int sit;
    		scanf("%d",&sit);
    		if(sit & 1)
    		{
    			int u,v;
    			scanf("%d%d",&u,&v);
    			if(exist[u]||exist[v]) continue;
    			int fu = find_set(u),fv = find_set(v);
    			if(fu != fv) rt[fu] = rt[fv] = merge(fu,fv);
    		} else {
    			int x;
    			scanf("%d",&x);
    			if(exist[x])
    			{
    				printf("-1
    ");
    				continue;
    			}
    			x = find_set(x);
    			printf("%d
    ",heap[x].val);
    			exist[x] = 1;
    			rt[heap[x].l] = rt[heap[x].r] = rt[x] = merge(heap[x].l,heap[x].r); 
    			heap[x].l = heap[x].r = dis[x] = 0;
    		}
    	}
    	return 0;
    }
    

    稍微难一点的板子题

    提升一下:

    分成多个不下降的子序列,求出每个子序列的中位数

    对于连续上升的子序列的中位数

    我们把她合并为一个堆 再求中位数

    求中位数

    一个堆的中位数等于根的权值的充要条件就是

    这个堆只有我们要求得堆的大小的一半(此时此堆仍是大(小)根堆)

    稍微想一下应该就可以理解了吧QAQ

    (Code)

    #include <cstdio>
    #include <cstring>
    #include <iostream>
    using namespace std;
    #define reg register int
    const int MAXN = 1e6 + 10;
    typedef long long ll;
    int n,num[MAXN],son[MAXN][2];
    template<typename T>
    inline T Read(T x)
    {
    	x = 0;
    	int f = 1;
    	char a = getchar();
    	while(!isdigit(a)) {if(a == '-') f = -1;a = getchar();} 
    	while(isdigit(a)) {x = (x << 1) + (x << 3) + (a ^ '0');a = getchar();}
    	return x * f;
    }
    namespace leftist_tree {
    	struct node {
    		int size,lef,rig,val,rt;
    	};
    	node heap[MAXN];
    	int dis[MAXN];
    	inline int merge(int u,int v) {
    		if(!u||!v) return u | v;
    		if(num[u] < num[v]||(num[u] == num[v]&&v > u)) swap(u,v);
    		son[u][1] = merge(son[u][1],v);
    		if(dis[son[u][0]] < dis[son[u][1]]) swap(son[u][0],son[u][1]);
    		dis[u] = dis[son[u][1]] + 1;
    		return u;
    	}
    }
    using namespace leftist_tree;
    inline void init() {
    	n = Read(1);
    	dis[0] = -1;
    	memset(heap,0,sizeof(heap));
    	for(reg i = 1; i <= n; i++)
    	{
    		num[i] = Read(1) - i;	
    	}
    }
    int cnt,b[MAXN];
    ll ans;
    inline void solve() {
    	for(reg i = 1; i <= n; i++) {
    		heap[++cnt] = (node){1,i,i,num[i],i};
    		while(cnt > 1&&heap[cnt - 1].val > heap[cnt].val) {
            对于连续上升的子序列的中位数
    	
    			我们把她合并为一个堆 再求中位数
    			cnt--;
    			heap[cnt].rt = merge(heap[cnt].rt,heap[cnt + 1].rt);
    			heap[cnt].size += heap[cnt + 1].size;
    			heap[cnt].rig = heap[cnt + 1].rig;
    			while(heap[cnt].size * 2 > heap[cnt].rig - heap[cnt].lef + 2) {
    				heap[cnt].size--;
    				heap[cnt].rt = merge(son[heap[cnt].rt][0],son[heap[cnt].rt][1]);
    			}
              只保留一半元素
    			heap[cnt].val = num[heap[cnt].rt];
    		}
    	}
    	for(reg i = 1; i <= cnt; i++)
    		for(reg j = heap[i].lef; j <= heap[i].rig; j++) {
    			b[j] = heap[i].val;
    			ans += 1ll * abs(b[j] - num[j]);
    		}
    	printf("%lld
    ",ans);
    }
    int main() {
    	init();
    	solve();
    	return 0;
    }
    
  • 相关阅读:
    数据库操作
    jquery 第一章
    算法很美 第一章
    python 学习第四天
    python学习第三天
    python学习第二天
    学习python的第一天
    C#-线程
    C#-流、存储
    C#-集合
  • 原文地址:https://www.cnblogs.com/resftlmuttmotw/p/11627850.html
Copyright © 2011-2022 走看看