左偏树
基操
-
合并两棵左偏树,时间复杂度\(O(nlgn)\)
-
删除最小节点:直接找根,合并左右子树
-
构造左偏树:建立一个队列,把每个节点放入,每次合并队首的左偏树,将其放入队尾,时间复杂度\(O(n)\)
-
删除已知节点(不是需要查询的值,而是节点编号):合并该点的左右儿子,然后接回去,更新\(dis\),更新到\(dis\)不再改变为止,时间复杂度\(O(nlgn)\)
-
查询值:最坏\(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
查询最坏是\(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
每次删除时需要把删掉的节点扔回去,否则有些节点的父亲会变成删掉的节点
#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;
}

左偏树,spfa,思维题
浙公网安备 33010602011771号