左 偏 树

用来维护堆的集合;
为什么不能直接暴力,因为这样子会退化成是一条链,然后就报废了;
那么如果我们要及时完成合并,就需要平衡;
两种方法,第一种是随机数,第二种是通过一些奇怪的方法维护平衡的性质;、
左偏树,因为他总是能保证树的一边,也就是右边是平衡的;
这要得益于他设计出来的dist性质以及,左偏性质的结合,使得链只有可能出现在右边,然后我们就方便干事情了;合并的时候维护左偏性质就可以了;

#include<iostream>
#include<cstdio>
using namespace std;
const int N = 200010;
int n;	
int v[N], dist[N], l[N], r[N], idx;
int p[N];
bool cmp (int x,int y) {
	if(v[x] != v[y]) return v[x] < v[y];
	return x < y;
} 		
int find(int x) {
	if(p[x] != x) p[x] = find(p[x]);
	return p[x];
}		
int merge(int x,int y) {
	if(!x || !y ) return x + y;
	if(cmp(y,x)) swap(x,y);
	r[x] = merge(r[x],y);
	if(dist[r[x]] > dist[l[x]] ) swap(l[x], r[x]);
	dist[x] = dist[r[x]] + 1;
	return x;
}		
int main() 
{		
	scanf("%d",&n);
	v[0] = 2e9;
	while (n -- ) {
		int t, x, y;
		scanf("%d%d",&t,&x);
		if (t == 1) {
			v[++idx] = x;
			dist[idx] = 1; 
			p[idx] = idx;
		} else if (t == 2) {
			scanf("%d",&y);
			x = find(x) , y = find(y);
			if (x != y) {
				if( cmp(y,x) ) swap(x,y);
				p[y] = x; 
				merge(x,y);
			}
		}else {
			if(t == 3) {
				printf("%d\n", v[find(x)]);
			}
		}else {
			x = find(x);
			if (cmp(r[x], l[x]))  swap(l[x], r[x]);
			p[x] = l[x], p[l[x]] = l[x];
			merge(l[x], r[x]);
		}
	}		
	return 0;
}		

P4331

这题是真的很牛逼很巧妙;
利用了从区间贪心扩展成整体最优解的形式,用了两个显而易见的结论草出来这道题;
我提一下这题值得借鉴的几个很巧妙的点就在于;
①化递增为非递减的方法,让我们的b可以重复从而搞出中位数;
②区间贪心到整体贪心;
③区间贪心时依然不满足题目的条件的,在对区间进行合并的时候又用到了新的贪心,以上;

#include<bits/stdc++.h>
using namespace std;
#define rep( i, s, t ) for( register int i = s; i <= t; ++ i )
#define re register
#define ls(x) ch[0][x]
#define rs(x) ch[1][x]
#define int long long
int read() {
	char cc = getchar(); int cn = 0, flus = 1;
	while(cc < '0' || cc > '9') {  if( cc == '-' ) flus = -flus;  cc = getchar();  }
	while(cc >= '0' && cc <= '9')  cn = cn * 10 + cc - '0', cc = getchar();
	return cn * flus;
}
const int N = 1e6 + 5;
int n, top, ans;
int a[N], b[N], ch[2][N], dis[N];
struct node {
	int rt, l, r, sz, val;
} s[N];
int merge(int x,int y) {
	if(!x || !y ) return x + y;
	if(a[x] < a[y]) swap(x,y);
	rs(x) = merge(rs(x), y);
	if( dis[rs(x) > dis[ls(x)]] ) swap(rs(x), ls(x));
	dis[x] = dis[rs(x)] + 1 ; 	return x;
}
int del(int x) { return merge(ls(x), rs(x)); } 
signed main() 
{
	n = read(); dis[0] = -1;
	rep(i, 1, n) a[i] = read() - i;
	rep(i, 1, n) {
		s[++top] = (node ){ i,i,i,1,a[i]};
		while(top != 1 && s[top - 1].val > s[top].val ) {
			-- top;	s[top].rt = merge(s[top].rt, s[top + 1].rt);
			s[top].sz += s[top + 1].sz; s[top].r = s[top +1].r;
			while(s[top].sz > (s[top].r - s[top].l + 2) / 2) {
				-- s[top].sz , s[top].rt = del(s[top].rt);
			}
			s[top].val = a[s[top].rt];
		}
	}
}

POJ3016

个人认为是相当具有思维含量的一道左偏树的例题;深刻的揭示了左偏树的本质,还套上了一层DP,很考验模型化思维的一道题目;
题目中要求把序列分成是几个单调序列。
然后这一步却是非常难想到,因为我们发现数据范围很小,所以可以采用DP来解最后一步,然后列出方程我们发现自己只需要求一下把每个序列给转变成最小那啥序列的一个花费即可。
然后对于求花费,我们能比较自然的想到可以用左偏树来求,因为左偏树的上一个模板题,就是让你做这个事情,然后就可以了;
那么我们开始考虑这个求花费的过程怎么用左偏树来实现;
我认为有两种实现方法;
第一种就是跟上面一样直接套那个板子;
第二种就是利用一些比较奇怪的性质;
这篇题解里面的内容能够减少一些时间,于是到这里,我想左偏树能够干什么的问题应该是明了了;
copy来的代码

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 1010;

int n, m;
int f[N][11];  // f[i, j] 把序列从1到i分为j个单调区间的最小代价
int cost[N][N];  // cost[i][j]表示将从i到j的序列变成单调序列的cost
int w[N], v[N], dist[N], l[N], r[N];

struct Segment
{
    int root;   // 左偏树的根编号
    int tot_size;  // 左偏树所维护的序列的元素总个数(包括大于左偏树树顶的元素)

}stk[N];

int merge(int x, int y)
{
    if (!x || !y) return x + y;
    if (v[x] < v[y]) x ^= y ^= x ^= y;
    r[x] = merge(r[x], y);
    if (dist[r[x]] > dist[l[x]]) l[x] ^= r[x] ^= l[x] ^= r[x];
    dist[x] = dist[r[x]] + 1;
    return x;
}

int pop(int x)
{
    return merge(l[x], r[x]);
}

void get_cost(int u)
{
    int top = 0, res = 0;  // res保存的是单调栈内存在的树的总cost
    for (int i = u; i <= n; ++i)  // 从u到n构造序列
    {
        // 根编号, 总元素个数
        auto cur = Segment({i,  1});
        l[i] = r[i] = 0;  // 初始化, 要正求一次负求一次所以要这样
        // 合并两颗左偏树
        while (top && v[cur.root] < v[stk[top].root])
        {

            if (cur.tot_size & 1) res += v[stk[top].root] - v[cur.root];

            // 如果个数都是奇数, 那么合并两个堆之后要保持中位数性质, 需要再丢掉1个数
            // 比如一个5里面都取3, 两个5就是10里面只能取第5个
            bool is_pop = cur.tot_size % 2 && stk[top].tot_size % 2; 

            // 合并2个左偏树
            cur.root = merge(cur.root, stk[top].root);
            cur.tot_size += stk[top].tot_size;
            if (is_pop) cur.root = pop(cur.root);
            top -- ;

        }
        stk[ ++ top] = cur;
        cost[u][i] = min(cost[u][i], res);
    }
}

int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; ++i) scanf("%d", &w[i]);
    memset(cost, 0x3f, sizeof cost);
    for (int i = 1; i <= n; ++i) v[i] = w[i] - i;
    for (int i = 1; i <= n; ++i) get_cost(i);

    for (int i = 1; i <= n; ++i) v[i] = -w[i] - i;
    for (int i = 1; i <= n; ++i) get_cost(i);



    memset(f, 0x3f, sizeof f);
    f[0][0] = 0;
    for (int i = 1; i <= n; ++i)
        for (int j = 1; j <= m; ++j)
            for (int k = 1; k <= i; ++k)
                f[i][j] = min(f[i][j], f[i - k][j - 1] + cost[i - k + 1][i]);

    printf("%d\n", f[n][m]);

    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
可持久化左偏树是一种支持历史版本查询的数据结构,它可以在不破坏原有数据结构的基础上,对其进行修改和查询。下面是一个简单的C++可持久化左偏树的实现: ```c++ #include <bits/stdc++.h> using namespace std; const int MAXN = 1e5 + 5; struct Node { int val, ls, rs; } t[MAXN * 40]; int n, m, cnt, root[MAXN]; int build(int l, int r) { int p = ++cnt; if (l == r) { t[p].val = 0; return p; } int mid = (l + r) >> 1; t[p].ls = build(l, mid); t[p].rs = build(mid + 1, r); return p; } int update(int pre, int l, int r, int x, int v) { int p = ++cnt; t[p] = t[pre]; if (l == r) { t[p].val += v; return p; } int mid = (l + r) >> 1; if (x <= mid) t[p].ls = update(t[pre].ls, l, mid, x, v); else t[p].rs = update(t[pre].rs, mid + 1, r, x, v); return p; } int query(int u, int v, int l, int r, int k) { if (l == r) return l; int mid = (l + r) >> 1; int cnt = t[t[v].ls].val - t[t[u].ls].val; if (cnt >= k) return query(t[u].ls, t[v].ls, l, mid, k); else return query(t[u].rs, t[v].rs, mid + 1, r, k - cnt); } int main() { scanf("%d%d", &n, &m); root[0] = build(1, n); for (int i = 1; i <= n; i++) { int x; scanf("%d", &x); root[i] = update(root[i - 1], 1, n, x, 1); } while (m--) { int l, r, k; scanf("%d%d%d", &l, &r, &k); printf("%d\n", query(root[l - 1], root[r], 1, n, k)); } return 0; } ``` 上述代码实现了一个可持久化左偏树,支持区间第k小查询。其中,build函数用于建立一棵空,update函数用于在原有版本的基础上插入一个新节点,query函数用于查询区间第k小。在主函数中,我们首先建立一棵空,然后依次插入每个节点,最后进行m次区间查询。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值