定义:
定义一个点如果没有左右子树中的一个为0点。
d0表示这个点倒0点的最小距离
而左偏树的定义是:
因此一直走rs的那条链长度是O(logn)的。[因为深度为的链,点数至少为]
作用:
- 合并两个堆:
维护两个指针u,v一开始都指向两个堆顶;
若(小根堆),把rs[v]合并到u上面。即
否则同理(交换一下即可);
左儿子就不用更新了。
最后比较一下:若交换一下, - 插入:把插入的数当作大小为1的堆
- 删除堆顶:合并左右子树
例题:
- 题意:sequence
- 思路:
首先每个a[i]-=i,b[i]-=i这样就可以能保证差值不变且递增序列变成了不递减序列。
然后我们把相同b[i]并成一个块(显然这歌块的值b[i]=原来这段区间a[i]的中位数*),然后就是一个递增的块的序列了。每新加了一个点(值),如果它比最后一个块值大,就自成一个新块。否则就加到最后一个块里面。伴随的连锁反映可能会让最后一个快b[i]<b[i-1]就让块i和前面合并……
问题是如何维护中位数?
每个块用一个大根堆堆存储中位数及比它小的数(堆顶为中位数)
ps.性质:两个序列原来的中位数分别为,,合并后中位数
因此b[i]和b[i-1]合并:b[i-1]>b[i]所以后面合并得的中位数
因此b[i-1]得证。
块i里面有且仅有存在一个数且这个结论才不存在
实际上块i里面的数肯定是的,它肯定入过堆的,在它弹出去前肯定也当过堆顶。而那时块i没有和前面合并的。所以。
性质得证……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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· winform 绘制太阳,地球,月球 运作规律
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人