左偏树
左偏树
可以发现左偏树的别名叫可并堆,就是可合并的堆。于是我们的主要操作便是合并。
我们定义外节点为左儿子或右儿子至少有一个为空的节点。我们定义外节点的
左偏树是一棵二叉树,首先其具有堆的性质,并且它是左偏的,每个节点的左儿子的
然后说一下
注意:左偏树的深度没有保证。
考虑类似于线段树合并的过程,合并两棵树时,比方说一个节点是
然后就是因为是小根堆,所以我们把权值更小的那个放在上面,另一个合并到右儿子即可。
合并完了之后,我们就要维护左偏的性质,考虑如果不满足,则交换左右儿子即可。最后维护一下
部分代码:
int merge(int x,int y){
if(x==0||y==0)return x+y;
if(a[x]>a[y])swap(x,y);
tr[x].r=merge(tr[x].r,y);
if(tr[tr[x].l].d<tr[tr[x].r].d)swap(tr[x].l,tr[x].r);
tr[x].d=tr[tr[x].r].d+1;
return x;
}
然后就是删除最小数,显然因为维护的是小根堆,所以删除的一定是堆顶,于是我们合并堆顶的两个儿子即可。
最后就是一些细节,拿一个数组记录一下这个数被删过没有,用来处理一些特殊情况,例如输出 -1
。
代码:
#include<bits/stdc++.h>
#define int long long
#define N 100005
using namespace std;
int n,m,fa[N];
bool st[N];
struct node{
int id,val;
bool operator>(const node &t)const{
if(val==t.val)return id>t.id;
return val>t.val;
}
}a[N];
struct tree{
int l,r,d;
}tr[N];
int find(int x){
return fa[x]==x?x:fa[x]=find(fa[x]);
}
int merge(int x,int y){
if(x==0||y==0)return x+y;
if(a[x]>a[y])swap(x,y);
tr[x].r=merge(tr[x].r,y);
if(tr[tr[x].l].d<tr[tr[x].r].d)swap(tr[x].l,tr[x].r);
tr[x].d=tr[tr[x].r].d+1;
return x;
}
signed main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>a[i].val;
fa[i]=a[i].id=i;
}
while(m--){
int op,x,y;
cin>>op>>x;
if(op==1){
cin>>y;
if(st[x]||st[y])continue;
int fx=find(x),fy=find(y);
if(fx!=fy)fa[fx]=fa[fy]=merge(fx,fy);
}
else{
if(st[x])cout<<"-1\n";
else{
int fx=find(x);
cout<<a[fx].val<<'\n';
st[fx]=1;
fa[tr[fx].l]=fa[tr[fx].r]=fa[fx]=merge(tr[fx].l,tr[fx].r);
tr[fx].l=tr[fx].r=tr[fx].d=0;
}
}
}
return 0;
}
派遣
首先我们假设确定了选择的根节点,那么我们显然要优先选择花费更少的点去产生贡献。所以,我们根据花费建出一个大根可并堆,然后依次删除根直到堆被删空或者剩下的点的花费和不超过
接下来每次合并父亲上的左偏树与儿子上的左偏树,类似一个动态规划的过程,然后就做完了。
左偏树的合并与删除堆顶都在上一道题有所提及,这里就不过多赘述了。
代码:
#include<bits/stdc++.h>
#define int long long
#define N 100005
#define M 200005
using namespace std;
int n,m,c[N],p[N],rt[N],res;
int h[N],e[M],ne[M],idx;
void add(int a,int b){
e[idx]=b;ne[idx]=h[a];h[a]=idx++;
}
struct node{
int l,r,d,cost,sum,siz;
}tr[N];
void pushup(int u){
tr[u].sum=tr[tr[u].l].sum+tr[tr[u].r].sum+tr[u].cost;
tr[u].siz=tr[tr[u].l].siz+tr[tr[u].r].siz+1;
}
int merge(int x,int y){
if(!x||!y)return x+y;
if(tr[x].cost<tr[y].cost)swap(x,y);
tr[x].r=merge(tr[x].r,y);
if(tr[tr[x].l].d<tr[tr[x].r].d)swap(tr[x].l,tr[x].r);
tr[x].d=tr[tr[x].r].d+1;
pushup(x);
return x;
}
int split(int x){
return merge(tr[x].l,tr[x].r);
}
void dfs(int u,int fa){
tr[u]={0,0,0,c[u],c[u],1};
rt[u]=u;
for(int i=h[u];~i;i=ne[i]){
int j=e[i];
if(j==fa)continue;
dfs(j,u);
rt[u]=merge(rt[u],rt[j]);
}
while(tr[rt[u]].sum>m&&tr[rt[u]].siz!=0){
rt[u]=split(rt[u]);
}
res=max(res,tr[rt[u]].siz*p[u]);
}
signed main(){
cin>>n>>m;
memset(h,-1,sizeof h);
for(int i=1;i<=n;i++){
int fa;
cin>>fa>>c[i]>>p[i];
add(fa,i);add(i,fa);
}
dfs(1,0);
cout<<res;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】