左偏树

基操

oi-wiki

  1. 合并两棵左偏树,时间复杂度\(O(nlgn)\)

  2. 删除最小节点:直接找根,合并左右子树

  3. 构造左偏树:建立一个队列,把每个节点放入,每次合并队首的左偏树,将其放入队尾,时间复杂度\(O(n)\)

  4. 删除已知节点(不是需要查询的值,而是节点编号):合并该点的左右儿子,然后接回去,更新\(dis\),更新到\(dis\)不再改变为止,时间复杂度\(O(nlgn)\)

  5. 查询值:最坏\(O(n)\)
    注意0节点的dis值为-1

模板

非可持久化:

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(ls[x],rs[x]);
	dis[x]=dis[rs[x]]+1;
	return x; 
}

可持久化:(节点开\(2ngln\)倍(能开多少就多少呗)差不多了)

int merge(int x,int y) {
	if(!x||!y) return x|y;
	if(a[x]>a[y]) swap(x,y);
	int z=++cnt; 
	ls[z]=ls[x],rs[z]=merge(rs[x],y),a[z]=a[x],b[z]=b[x];//注意更新
	if(dis[rs[z]]>dis[ls[z]]) swap(ls[z],rs[z]);
	dis[z]=dis[rs[z]]+1;
	return z; 
}

例题1

Luogu P3377 【模板】左偏树(可并堆)

查询最坏是\(O(n)\)在找根的时候需要路径压缩
否则一条链,即\(10000,9999,9998,9997\cdot\cdot\cdot\)的情况暴力查找是\(O(n)\)

#include<bits/stdc++.h>
using namespace std;

const int N=1e5+5;
int n,m,f[N],ls[N],rs[N],a[N],d[N];

inline int find(int x) {
	return x==f[x]?x:f[x]=find(f[x]);
}

int merge(int x,int y) {
	if(!x||!y) return x+y;
	if(a[x]>a[y]||a[x]==a[y]&&x>y) swap(x,y);
	rs[x]=merge(rs[x],y); f[rs[x]]=x;
	if(d[rs[x]]>d[ls[x]]) {
		swap(ls[x],rs[x]);
	}
	d[x]=d[rs[x]]+1;
	return x;
}

void del(int x) {
	a[x]=-1;
	f[ls[x]]=ls[x],f[rs[x]]=rs[x];
	f[x]=merge(ls[x],rs[x]);
}
int main() {
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++) {
		f[i]=i; scanf("%d",&a[i]);
	}
	for(int i=1;i<=m;i++) {
		int op; scanf("%d",&op);
		if(op==1) {
			int x,y; scanf("%d%d",&x,&y);
			if(a[x]!=-1&&a[y]!=-1) {
				x=find(x),y=find(y);
				if(x!=y) merge(x,y);
			}
		} else {
			int x; scanf("%d",&x);
			if(a[x]!=-1) {
				x=find(x);
				printf("%d\n",a[x]);
				del(x);
			} else puts("-1");
		}
	}
	return 0;
}

例题2

Luogu P2713 罗马游戏

每次删除时需要把删掉的节点扔回去,否则有些节点的父亲会变成删掉的节点

#include<bits/stdc++.h>
const int INF=1e9;
using namespace std;

const int N=1e6+5;
int f[N],a[N],ls[N],rs[N],dis[N],n;
char op[10];

inline int find(int x) {
	int r=x,u;
	while(f[r]) r=f[r];
	while(f[x]) {
		u=x,x=f[x],f[u]=r;
	}
	return r;
}
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); f[rs[x]]=x;
	if(dis[ls[x]]<dis[rs[x]]) swap(ls[x],rs[x]);
	dis[x]=dis[rs[x]]+1;
	return x;
}

inline void del(int x) {
	a[x]=INF;
	f[ls[x]]=0,f[rs[x]]=0;
	int t=merge(ls[x],rs[x]);
	ls[x]=rs[x]=dis[x]=f[x]=0;
	merge(t,x);
}

int main() {
	scanf("%d",&n); dis[0]=-1;
	for(int i=1;i<=n;i++) {
		scanf("%d",&a[i]);
	}
	int T; scanf("%d",&T);
	while(T--) {
		scanf("%s",op);
		if(op[0]=='M') {
			int u,v; scanf("%d%d",&u,&v);
			if(a[u]!=INF&&a[v]!=INF) {
				u=find(u),v=find(v);
				if(u!=v) merge(u,v);
			}
		} else {
			int u; scanf("%d",&u);
			if(a[u]==INF) puts("0");
				else {
					u=find(u);
					printf("%d\n",a[u]);
					del(u);
				}
		}
	}
	return 0;
}

例题3

[ybtoj 城池攻占] (https://www.ybtoj.com.cn/contest/84/problem/4)

加,乘标记,Luogu上死活过不去

#include<bits/stdc++.h>
#define ll long long
using namespace std;

ll ret; char ch; bool fh;
inline ll rd() {
	for(fh=0,ch=getchar();ch<'0'||ch>'9';ch=getchar()) {
		if(ch=='-') fh=1;
	}
	for(ret=0;ch>='0'&&ch<='9';ch=getchar()) {
		ret=(ret<<1)+(ret<<3)+ch-'0';
	}
	return fh?-ret:ret;
}

const int N=3e5+5;
int n,m,cnt,dis[N],d[N],ls[N],rs[N],rt[N],ans[N],Ans[N],fro[N];
ll h[N],t1[N],t2[N],a[N];
bool op[N];
vector<int>V[N];
struct A{ int id; ll x; }c[N];

inline int New(A x) {
	ls[++cnt]=rs[cnt]=d[cnt]=t1[cnt]=0,t2[cnt]=1,c[cnt]=x;
	return cnt;
}

inline void Tim(int p,ll x) {
	if(p) c[p].x*=x,t1[p]*=x,t2[p]*=x;
}
inline void Add(int p,ll x) {
	if(p) c[p].x+=x,t1[p]+=x; 
}
inline void down(int p) {
	if(t2[p]>1) {
		Tim(ls[p],t2[p]),Tim(rs[p],t2[p]),t2[p]=1;
	}
	if(t1[p]!=0) {
		Add(ls[p],t1[p]),Add(rs[p],t1[p]),t1[p]=0;	
	}
}
int merge(int u,int v) {
	if(!u||!v) return u|v;
	down(u),down(v);
	if(c[u].x>c[v].x) swap(u,v);
	rs[u]=merge(rs[u],v);
	if(d[rs[u]]>d[ls[u]]) swap(ls[u],rs[u]);
	d[u]=d[rs[u]]+1;
	return u;
}

void dfs(int u) {
	for(auto v:V[u]) {
		dfs(v);
		rt[u]=merge(rt[u],rt[v]);
	}
	for(down(rt[u]);rt[u]&&c[rt[u]].x<h[u];rt[u]=merge(ls[rt[u]],rs[rt[u]]),down(rt[u])) {
		Ans[c[rt[u]].id]=dis[fro[c[rt[u]].id]]-dis[u];
		ans[u]++;
	}
	if(u!=1){
		if(op[u]) Tim(rt[u],a[u]);
			else Add(rt[u],a[u]);
	} else {
		for(;rt[u];rt[u]=merge(ls[rt[u]],rs[rt[u]])) {
			Ans[c[rt[u]].id]=dis[fro[c[rt[u]].id]];
		}
	}
}
queue<int>Q;
inline void bfs() {
	Q.push(1); dis[1]=1;
	while(!Q.empty()) {
		int u=Q.front(); Q.pop();
		for(auto v:V[u]) {
			dis[v]=dis[u]+1,Q.push(v);
		}
	}
}

int main(){
	n=rd(),m=rd(); 
	for(int i=1;i<=n;i++) h[i]=rd();
	for(int i=2;i<=n;i++) {
		V[rd()].push_back(i);
		op[i]=rd(),a[i]=rd();
	}
	for(int i=1;i<=m;i++) {
		ll u=rd(),v=rd();
		rt[v]=merge(rt[v],New((A){i,u}));
		fro[i]=v;
	}
	bfs();
	dfs(1);
	for(int i=1;i<=n;i++) printf("%d\n",ans[i]);
	for(int i=1;i<=m;i++) printf("%d\n",Ans[i]);
	return 0;
}

例题4

Luogu P2483 【模板】k短路 / [SDOI2010]魔法猪学院

卡精度的辣鸡题

论文里说的很好了

注意,可能有重边,所以找最短路径树时要小心(我之前只算了一次),合并左偏树也要小心,别合并多次

#include<bits/stdc++.h>
#define db long double
#define ll long long
using namespace std;

const int N=5005,M=1e6+5;
int n,m,cnt,dis[M],b[M],ls[M],rs[M],rt[N];
bool fl[N];
ll d[N],a[M];
struct A{
	int v; ll w;
}pre[N];
vector<A>V[N],Vf[N];
queue<int>Q;

inline void spfa() {
	for(int i=1;i<=n;i++) d[i]=1e18;
	Q.push(n),d[n]=0; fl[n]=1;
	while(!Q.empty()) {
		int u=Q.front(); fl[u]=0; Q.pop();
		for(auto v:Vf[u]) {
			if(d[v.v]>d[u]+v.w) {
				d[v.v]=d[u]+v.w,pre[v.v]=(A){u,v.w};
				if(!fl[v.v]) {
					fl[v.v]=1;
					Q.push(v.v);
				}
			}
		}
	} 
}

inline int New(ll x,int to) {
	a[++cnt]=x; b[cnt]=to;
	return cnt;
}

int merge(int x,int y) {
	if(!x||!y) return x|y;
	if(a[x]>a[y]) swap(x,y);
	int z=++cnt; 
	ls[z]=ls[x],rs[z]=merge(rs[x],y),a[z]=a[x],b[z]=b[x];
	if(dis[rs[z]]>dis[ls[z]]) swap(ls[z],rs[z]);
	dis[z]=dis[rs[z]]+1;
	return z; 
}
void bfs() {
	queue<int>().swap(Q);
	Q.push(n);
	while(!Q.empty()) {
		int u=Q.front(); Q.pop();
		for(auto v:Vf[u]) {
			if(!fl[v.v]&&pre[v.v].v==u) {
				fl[v.v]=1;
				Q.push(v.v);
				rt[v.v]=merge(rt[v.v],rt[u]);
			}
		}
	}
}

bool operator >(A i,A j){
	return i.w>j.w;
}
priority_queue<A,vector<A>,greater<A> >q;
int main(){
	ll E; db e; scanf("%d%d%Lf",&n,&m,&e); E=(ll)e*1000000;
	for(int i=1;i<=m;i++) {
		int u,v; db k; scanf("%d%d%Lf",&u,&v,&k); ll kk=k*1000000;
		Vf[v].push_back((A){u,kk});
		V[u].push_back((A){v,kk});
	}
	spfa(); dis[0]=-1; 
	for(int u=1;u<n;u++) {
		if(d[u]!=1e18) { 
			bool fl=0;
			for(auto v:V[u]) {
				if(!fl&&d[u]==d[v.v]+v.w&&v.v==pre[u].v) fl=1;
					else rt[u]=merge(rt[u],New(d[v.v]-d[u]+v.w,v.v));
			}
		}
	}
	bfs();
	if(E<d[1]) {
		puts("0"); return 0;
	}
	E-=d[1]; 
	q.push((A){rt[1],a[rt[1]]});
	int ans=1;
	while(!q.empty()&&E>d[1]) {
		A u=q.top(); q.pop();
		if(E<u.w+d[1]) break;
		E-=u.w+d[1]; ans++;
		if(ls[u.v]) q.push((A){ls[u.v],u.w+a[ls[u.v]]-a[u.v]});
		if(rs[u.v]) q.push((A){rs[u.v],u.w+a[rs[u.v]]-a[u.v]});
		if(rt[b[u.v]]) q.push((A){rt[b[u.v]],u.w+a[rt[b[u.v]]]});
	}
	printf("%d\n",ans);
	return 0;
}

例题5

Luogu P4331 [BalticOI 2004]Sequence 数字序列

现将\(a[i]\)\(b[i]\)同时减\(i\),显然对答案无影响,只是\(b[i]\)单调不降,这才有操作空间

有初中生知识点可知:

\(a\)单调减是取\(b\)都为中位数最优(\(b\)应该都一样)(此时b无论向上还是向下答案都会变大)

所以把它看做很多段单调减的曲线拼在一起,相邻两段\(b_i,b_{i+1}\)可以合并

如果\(b_i<=b_{i+1}\),保持原状,如果\(b_i>b_i+1\) ,需要将这两端拼起来去中位数

分析同上,如果\(b\)不一样,无论哪段向上还是向下都会使答案变大

所以考虑如何维护中位数,可以通过堆,每次堆里只放区间长度的一半的元素,堆顶即为答案

而两个堆合并后,中位数仍在其中,只需要删除到一半即可

由于只会删不会加,所以时间复杂度均摊\(O(nlgn)\)

#include<bits/stdc++.h>
#define ll long long
using namespace std;

inline int rd() {
	int ret=0; char ch=getchar();
	while(ch<'0'||ch>'9') ch=getchar();
	for(;ch>='0'&&ch<='9';ch=getchar()) ret=(ret<<1)+(ret<<3)+ch-'0';
	return ret;
}
const int N=1e6+6;
int n,sz[N],c[N],L[N],R[N],top,st[N],a[N],b[N],cnt,ls[N],rs[N],d[N];
inline int New(int x) {
	c[++cnt]=x; sz[cnt]=1; return cnt;
}
int merge(int u,int v) {
	if(!u||!v) return u|v;
	if(c[u]<c[v]) swap(u,v);
	rs[u]=merge(rs[u],v);
	sz[u]=sz[ls[u]]+sz[rs[u]]+1;
	if(d[ls[u]]<d[rs[u]]) swap(ls[u],rs[u]);
	d[u]=d[rs[u]]+1; 
	return u;
}
int main(){
	n=rd(); d[0]=-1;
	for(int i=1;i<=n;i++) a[i]=rd()-i;
	for(int i=1;i<=n;i++) {	
		int rt=New(a[i]);
		for(;top&&c[st[top]]>c[rt];top--) {
			rt=merge(rt,st[top]);
			while(sz[rt]>(i-L[st[top]]+1+1>>1)) {
				rt=merge(ls[rt],rs[rt]);
			}
		}
		st[++top]=rt;
		L[st[top]]=R[st[top-1]]+1,R[st[top]]=i;
	}
	for(int i=1;i<=top;i++) {
		for(int j=L[st[i]];j<=R[st[i]];j++) b[j]=c[st[i]];
	}
	ll ans=0;
	for(int i=1;i<=n;i++) ans+=abs(b[i]-a[i]);
	printf("%lld\n",ans);
	for(int i=1;i<=n;i++) {
		printf("%d%c",b[i]+i,i==n?'\n':' ');
	}
	return 0;
}
posted @ 2021-01-04 21:12  wwwsfff  阅读(97)  评论(0)    收藏  举报