定义:

定义一个点如果没有左右子树中的一个为0点。
d0表示这个点倒0点的最小距离
而左偏树的定义是:\(d0[ls]>=d0[rs]\)
因此一直走rs的那条链长度是O(logn)的。[因为深度为\(n\)的链,点数至少为\(2^n - 1\)]

作用:

  • 合并两个堆
    维护两个指针u,v一开始都指向两个堆顶;
    \(val[u]<val[v]\)(小根堆),把rs[v]合并到u上面。即\(rs[u]=Merge(u,rs[v])\)
    否则同理(交换一下即可);
    左儿子就不用更新了。
    最后比较一下:若\(d0[ls]<d0[rs]\)交换一下\(ls\),\(rs\)
  • 插入:把插入的数当作大小为1的堆
  • 删除堆顶:合并左右子树

例题:

  • 题意:sequence
  • 思路:
    首先每个a[i]-=i,b[i]-=i这样就可以能保证差值不变且递增序列变成了不递减序列
    然后我们把相同b[i]并成一个块(显然这歌块的值b[i]=原来这段区间a[i]的中位数*),然后就是一个递增的块的序列了。每新加了一个点(值),如果它比最后一个块值大,就自成一个新块。否则就加到最后一个块里面。伴随的连锁反映可能会让最后一个快b[i]<b[i-1]就让块i和前面合并……
    问题是如何维护中位数?
    每个块用一个大根堆堆存储中位数及比它小的数(堆顶为中位数)
    ps.性质:两个序列原来的中位数分别为\(x\),\(y\),\(x<y\)合并后中位数\([x,y)\)
    因此b[i]和b[i-1]合并:b[i-1]>b[i]所以后面合并得的中位数\(m<b[i-1]\)
    因此b[i-1]得证。
    块i里面有且仅有存在一个数\(a[j]>b[i]\)\(a[j]<b[i-1]\)这个结论才不存在
    实际上块i里面\(a[j]>b[i]\)的数肯定是\(a[j]>b[i-1]\)的,它肯定入过堆的,在它弹出去前肯定也当过堆顶。而那时块i没有和前面合并的。所以\(a[j]>b[i-1]\)
    性质得证……qaq
  • 代码:
    #include<bits/stdc++.h>
    using namespace std;
    const int N=1e6+5;
    typedef long long ll;
    ll a[N];
    int cnt[N],L[N],R[N],rt[N],m,d0[N],ncnt;
    struct node {int ls,rs;ll val;}hp[N];
    int Nw(int w) {hp[++ncnt]=(node){0,0,w};return ncnt;}
    int Merge(int x,int y) {
    	if(!x||!y) return x+y;
    	if(hp[x].val<hp[y].val) swap(x,y);
    	hp[x].rs=Merge(hp[x].rs,y);
    	d0[x]=d0[hp[x].rs]+1;
    	if(d0[hp[x].ls]<d0[hp[x].rs]) swap(hp[x].ls,hp[x].rs);
    	return x;
    }
    int main() {
    	int n;
    	scanf("%d",&n);
    	for(int i=1;i<=n;i++) scanf("%lld",&a[i]),a[i]-=i;
    	rt[++m]=Nw(a[1]);L[1]=R[1]=cnt[1]=1;
    	for(int i=2;i<=n;i++) {
    		if(a[i]>hp[rt[m]].val) {rt[++m]=Nw(a[i]);L[m]=R[m]=i;cnt[m]=1;continue;}
    		rt[m]=Merge(rt[m],Nw(a[i])),R[m]=i,cnt[m]++;
    		while(cnt[m]>(R[m]-L[m]+2)/2) {
    			rt[m]=Merge(hp[rt[m]].ls,hp[rt[m]].rs); cnt[m]--;
    		}
    		while(m>1&&hp[rt[m]].val<=hp[rt[m-1]].val) {
    			rt[m-1]=Merge(rt[m-1],rt[m]);cnt[m-1]+=cnt[m];R[--m]=i;
    			while(cnt[m]>(R[m]-L[m]+2)/2) {
    				rt[m]=Merge(hp[rt[m]].ls,hp[rt[m]].rs); cnt[m]--;
    			}
    		}
    	}
    	ll ans=0;
    	for(int i=1;i<=m;i++) {
    		for(int j=L[i];j<=R[i];j++) {
    			ans+=abs(a[j]-hp[rt[i]].val);
    		}
    	}
    	printf("%lld",ans);
    	return 0;
    }

碾压沥青树

  • 题意:树上的sequence
  • 思路:
    ps.nodgd证明木有听懂了。zz的做法记住了,想了好会儿又问了一下,突然就懂了。真的很nice~
    我们dfs从叶子更新到根,我们首先每个叶子以她权值为一个堆,然后每个点u先把下面儿子v的堆并起来。
    然后考虑若堆顶值为val,a[u]>val,直接把a[u]+进堆。
    否则ans+=val-a[u],把堆顶弹出,加入两个au
    证明?
    首先这个式子在(1.a[u]>val2(次大值)显然成立,2.a[u]<val2,当前是把堆顶和b[u]调到val2,但实际给val-a[u]的下限为a[u],不过这暂时还娶不到要等val2,但是将来val2降下来就有可能!)
    堆里存的并不是b[i]而是代价为当前ans,b[i]能最多满足的下限。(这个下限并不是所有时刻都能满足的,但是在堆顶的时候是肯定能满足,没有能制约他的)
    因为堆顶能满足,这个贪心策略显然就是对的。
    (在同等代价下,每一步都是尽量往小的压)
  • code(也是非常好写)
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
typedef long long ll;
ll ans;
int rt[N],d0[N],ncnt,fa[N],nxt[N],to[N],head[N],ecnt,a[N];
struct heap {int ls,rs,val;}hp[N];
int Merge(int u,int v) {
	if(!u||!v) return u+v;
	if(hp[u].val<hp[v].val) swap(u,v);
	hp[u].rs=Merge(hp[u].rs,v);
	d0[u]=d0[hp[u].rs]+1;
	if(d0[hp[u].ls]<d0[hp[u].rs]) swap(hp[u].ls,hp[u].rs);
	return u;
}
int Nw(int w) {hp[++ncnt]=(heap){0,0,w};return ncnt;}
void add_edge(int u,int v) {nxt[++ecnt]=head[u];to[ecnt]=v;head[u]=ecnt;}
void dfs(int u) {
	bool flag=0;
	for(int i=head[u];i;i=nxt[i]) {
		int v=to[i];
		dfs(v);flag=1;
		if(!rt[u]) rt[u]=rt[v];
		else rt[u]=Merge(rt[u],rt[v]);
	}
	if(!flag) rt[u]=Nw(a[u]);
	else {
		if(a[u]>hp[rt[u]].val) rt[u]=Merge(rt[u],Nw(a[u]));
		else {
			ans+=hp[rt[u]].val-a[u];
			rt[u]=Merge(hp[rt[u]].ls,hp[rt[u]].rs);
//			int w=max(a[u],hp[rt[u]].val);
			int w=a[u];
			rt[u]=Merge(rt[u],Nw(w));
			rt[u]=Merge(rt[u],Nw(w));
		}
	}
}
int main() {
	int n;
	scanf("%d",&n);
	for(int i=2;i<=n;i++)scanf("%d",&fa[i]),add_edge(fa[i],i);
	for(int i=1;i<=n;i++)scanf("%d",&a[i]);
	dfs(1);
	printf("%lld",ans);
	return 0;
}