左偏树的简单总结

前言:

蒟蒻的我第一次看到左偏树这个名词的时候还在学习dfs。。并且还不会写树。。233

然后我仿佛打开了新世界的大门。。开始疯狂地点击百度跳出来的各种算法链接。。并成功看到了斐波那契堆二项堆斜堆左式堆之类的奇怪名词。。。之后在刷luogu试练场时出于好奇的心理点开了提高(?还是省选 忘了orz)常用模版题。。又看到了左偏树

于是我对左偏树产生了奇怪的执念。。。于是在很久很久以后我终于肝起了左偏树。。于是就有了这篇纪念意义大过实际意义的文章。(口胡了什么东西)

正文:

左偏树就是向左偏的树。。

这个性质无疑十分地妙。。它对于合并时log的复杂度起到了贡献。。而当给节点的权值加上“val[fa]<=val[son]”的限制时,

它就具有了堆的性质,可以拿来实现堆的合并了。

设dis[i]表示i往下走的最近的没有右子树的节点到i的距离。显然dis[i]=dis[rson]+1,dis[lson]>dis[rson]。

合并的时候我们认为只要dis是符合这个性质的那么就仍然是一棵合法的左偏树了。。

那么就转而维护这个性质即可。

合并:合并以x为根和以y为根的左偏树。

假设val[x]<=val[y]。 则将x的右儿子与y合并。

然后维护dis的限制,如果不符合dis[lson]>=dis[rson]了,就交换左右儿子。

显然可以发现不会对子树内那些已经合法的东西产生影响,也就是说我们总可以成功合并出一棵dis满足条件的左偏树。

如果有树空了就可以退出了。log(树的大小)是多么让人快乐啊。。

pop:合并左右儿子。

push:同合并,只不过是合并只有一个节点的树和一棵可能多节点的树而已。。

奇怪的操作:例如打标记,显然是支持的。这个要看具体题目了。

题目:

luogu3377模板题

#include<bits/stdc++.h>
using namespace std;
#define rep(x,y,z) for (int x=y; x<=z; x++)
#define downrep(x,y,z) for (int x=y; x>=z; x--)
#define ms(x,y,z) memset(x,y,sizeof(z))
#define LL long long
#define repedge(x,y) for (int x=hed[y]; ~x; x=edge[x].nex)
inline LL read(){
	LL x=0; int w=0; char ch=0;
	while (ch<'0' || ch>'9') w|=ch=='-',ch=getchar();
	while (ch>='0' && ch<='9') x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
	return w? -x:x;
}
const int N=100005;
int n,m,bel[N];
LL a[N];
struct node{ int lson,rson,fa,dis; LL val; }tr[N];
int merge(int x,int y){
	if (!x) return y; if (!y) return x;
	if (tr[x].val>tr[y].val) swap(x,y);
	tr[x].rson=merge(tr[x].rson,y);
	tr[tr[x].rson].fa=x; 
	if (tr[tr[x].rson].dis>tr[tr[x].lson].dis) swap(tr[x].lson,tr[x].rson);
	tr[x].dis=tr[tr[x].rson].dis+1; tr[x].fa=0;
	return x;
}
int pop(int x){
	int t1=tr[x].lson; int t2=tr[x].rson; bel[x]=0;
	return merge(t1,t2); 
}
void dfs(int x,int rot){
	if (!x) return;
	bel[x]=rot; dfs(tr[x].lson,rot); dfs(tr[x].rson,rot);
}
int main(){
	n=read(); m=read();
	rep(i,1,n){ a[i]=read(); bel[i]=i; tr[i].val=a[i]; tr[i].fa=tr[i].dis=tr[i].lson=tr[i].rson=0; }
	rep(i,1,m){
		int kd=read();
		if (kd==1){ 
		   int x=read(); int y=read();
		   int fx=bel[x]; int fy=bel[y];
		   if ((fx==fy)||(!fx)||(!fy)) continue;
		   int t=merge(fx,fy); dfs(t,t);
		}
		if (kd==2){
		   int x=read(); if (!bel[x]) { puts("-1"); continue; }
		   printf("%lld\n",tr[bel[x]].val);
		   int t=pop(bel[x]); dfs(t,t);
		}
	}
	return 0;
}

loj6131:(需要知道行列式的概念和n^3求法)

吐槽:此题就是传说中的打标记了。。。其实我本来是想要用set+启发式合并的,但是在百万面前还是退缩了。。。

首先构造出一个矩阵,a[i][l[i]~r[i]]=1;然后我们对于以i为第一个是1元素的所有行建立左偏树,插入的是这一行1的长度;

要消第i列时,用长度最短的那一行去消长度比较大的那些行,保证不会出现-1;然后将这个堆内所有行的起点右移最短行的长度,长度减去最短行的长度,就相当于去掉长度变零的,再并入代表着新起点j的左偏树里。维护长度则打标记就可以了。

然后就顺便记录下了消元后的每一行是本来的哪行。显然可以由此推出交换了几次。行列式为正,则Y;以此类推。

#include<bits/stdc++.h>
using namespace std;
#define rep(x,y,z) for (int x=y; x<=z; x++)
#define downrep(x,y,z) for (int x=y; x>=z; x--)
#define ms(x,y,z) memset(x,y,sizeof(z))
#define LL long long
#define repedge(x,y) for (int x=hed[y]; ~x; x=edge[x].nex)
inline int read(){
	int x=0; int w=0; char ch=0;
	while (ch<'0' || ch>'9') w|=ch=='-',ch=getchar();
	while (ch>='0' && ch<='9') x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
	return w? -x:x;
}
const int N=100005;
int n,m,ans[N],root[N],place[N],tmp[N];
struct rec{ int l,r; }o[N];
struct node{ int fa,lson,rson,val,laz,dis; }tr[N];
void pushdown(int x){
	if (!tr[x].laz) return;
	if (tr[x].lson) tr[tr[x].lson].val+=tr[x].laz,tr[tr[x].lson].laz+=tr[x].laz;
	if (tr[x].rson) tr[tr[x].rson].val+=tr[x].laz,tr[tr[x].rson].laz+=tr[x].laz;
	tr[x].laz=0;
}
int merge(int x,int y){
	if (!x) return y; if (!y) return x;
	pushdown(x); pushdown(y);
	if (tr[x].val>tr[y].val) swap(x,y); 
	tr[x].rson=merge(tr[x].rson,y);
	tr[tr[x].rson].fa=x;
	if (tr[tr[x].rson].dis>tr[tr[x].lson].dis) swap(tr[x].lson,tr[x].rson);
	tr[x].dis=tr[tr[x].rson].dis+1; tr[x].fa=0;
	return x;
}
int pop(int x){
	if (!x) return 0; pushdown(x);
	int t1=tr[x].lson; int t2=tr[x].rson;
	return merge(t1,t2);
}
int solve(int n){
	rep(i,1,n){ tr[i].val=o[i].r-o[i].l+1; tr[i].dis=tr[i].laz=tr[i].lson=tr[i].rson=tr[i].fa=0; }
	rep(i,1,n) root[i]=0; rep(i,1,n) root[o[i].l]=merge(root[o[i].l],i);
	rep(i,1,n){
		if (!root[i]) return 0; ans[i]=root[i]; int t=tr[root[i]].val; 
		while ((root[i])&&(tr[root[i]].val==t)) root[i]=pop(root[i]); 
		if (!root[i]) continue; tr[root[i]].laz-=t; tr[root[i]].val-=t; 
		int k=i+t; root[k]=merge(root[k],root[i]);
	}
	rep(i,1,n) place[i]=i,tmp[i]=i; int w=1;
	rep(i,1,n)if (tmp[i]!=ans[i]){ w*=(-1); 
	tmp[place[ans[i]]]=tmp[i]; place[tmp[i]]=place[ans[i]];
	place[ans[i]]=i; tmp[i]=ans[i]; }
	return w;
}
int main(){
	int cas=read();
	rep(cl,1,cas){
		n=read(); rep(i,1,n) o[i].l=read(),o[i].r=read();
		int ans=solve(n); if (ans>0) puts("Y"); else if (!ans) puts("D"); else puts("F");
	}
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值