配对堆
配对堆是可并堆的一种,支持插入、查询最小值、删除最小值、修改元素,但是无法可持久化。
配对堆是满足堆性质的带权多叉树,使用儿子-兄弟表示法存储。
在删除最小值时,为了保证总的均摊复杂度,需要把儿子们两两配对,再用merge把配成一对的两个儿子合并到一起,再将新产生的堆从右往左挨个合并。
这里的父指针只是并查集的父指针,由于操作中只有删除根节点,所以只需要知道根节点,使用路径压缩。
struct PairingHeap{
struct tree{
int v,c,s,f;
}t[N];
#define v(p) (t[p].v)
#define c(p) (t[p].c)/*指向该节点的第一个儿子*/
#define s(p) (t[p].s)/*sibling指向节点的下一个兄弟*/
#define f(p) (t[p].f)
int tot;
int find(int x){
return x==f(x)?x:f(x)=find(f(x));
}
inline int top(int x){/*返回x所在堆的最小值*/
if(v(x)==-1)return -1;
return v(find(x));
}
inline int pop(int p){/*返回并删除p所在堆的最小值*/
if(v(p)==-1)return -1;
p=find(p);/*找到根节点*/
int x=v(p),y=merge(c(p));/*合并所有儿子产生新根*/
f(p)=f(y)=y;/*并查集指向新根*/
v(p)=-1;
return x;
}
int merge(int x,int y){/*合并x和y两个堆*/
if(!x||!y)return x+y;
if(v(x)>v(y)||(v(x)==v(y)&&x>y))swap(x,y);
s(y)=c(x);/*y的兄弟链增添一个x的儿子*/
c(x)=y;/*y成为x的儿子*/
return x;
}
int merge(int p){/*合并p节点的所有兄弟*/
if(!p||!s(p))return p;
int x=s(p)/*p的下一个兄弟*/,y=s(x);/*再下一个兄弟*/
s(p)=s(x)=0;/*拆散*/
return merge(merge(y),merge(p,x));/*对兄弟链从右向左合并,注意这里的顺序*/
}
inline void mergeset(int x,int y){/*合并x和y所在的堆*/
if(v(x)==-1||v(y)==-1)return;
x=find(x),y=find(y);
if(x==y)return;
f(x)=f(y)=merge(x,y);
}
inline void create(int x){/*新建一个只含x的堆*/
t[++tot]={x,0,0,tot};
}
inline void push(int p,int x){/*在p所在的堆中插入x*/
t[++tot]={x,0,0,tot};
p=find(p);
f(p)=f(tot)=merge(p,tot);
}
}ph;
配对堆可以减小一个元素的值,这里需要维护它的父指针。
struct PairingHeap{
struct tree{
int c,s,v,f;
}t[N];
#define c(p) (t[p].c)
#define s(p) (t[p].s)
#define v(p) (t[p].v)
#define f(p) (t[p].f)
int find(int x){
return x==f(x)?x:find(f(x));
}
int merge(int x,int y){
if(!x||!y)return x|y;
if(v(x)>v(y))swap(x,y);
if(c(x))f(c(x))=y;
s(y)=c(x);
f(y)=x;
c(x)=y;
return x;
}
int merge(int p){
if(!p)return p;
f(p)=0;
if(!s(p))return p;
int x=s(p),y=s(x);
f(x)=s(p)=s(x)=0;
return merge(merge(y),merge(p,x));
}
int decrease(int x,int v){
int p=find(x);
v(x)=v;
if(x==p)return x;
if(c(f(x))==x)c(f(x))=s(x);
else s(f(x))=s(x);
if(s(x))f(s(x))=f(x);
s(x)=f(x)=0;
return merge(p,x);
}
};
维护多个大根堆,支持一种操作,将两个大根堆的根节点减少为原来的一半,之后合并两个大根堆并返回合并后的最大值。对于修改根节点的操作,可以先修改权值,之后取出根节点,再重新把根节点加入原来的堆中。
inline int modify(int p){
p=find(p);/*找到根节点*/
v(p)>>=1;/*修改权值*/
int x=merge(c(p));/*合并产生新根*/
c(p)=s(p)=0;/*孤立旧根*/
f(p)=f(x)=merge(p,x);/*合并新旧根为总根*/
return f(p);
}
inline int solve(int x,int y){
x=find(x),y=find(y);
if(x==y)return -1;
x=modify(x),y=modify(y);
f(x)=f(y)=merge(x,y);/*合并x与y*/
return v(f(x));
}
一棵有根树,每个点有领导力与费用,选一个点当领导,之后在这个点的子树中选择费用之和不超过m的点,利润为领导的领导力*选择的点的个数,领导可不被选择,求利润最大值。维护多个大根堆,从叶子节点往父亲那里合并,所有儿子合并完后,不断删除费用最大的人,直到费用总和<=m,即为该点的最优方案。注意对多叉树的pushup.
inline void pushup(int p){
sm(p)+=sm(c(p));
sz(p)+=sz(c(p));
}
inline int split(int p){
return merge(c(p));
}
int merge(int x,int y){
if(!x||!y)return x|y;
if(v(x)<v(y))swap(x,y);
s(y)=c(x);
c(x)=y;
pushup(x);
return x;
}
void dfs(int x){
v(x)=sm(x)=cost[x];
c(x)=s(x)=0;
sz(x)=1;
rt[x]=x;
for(auto y:v[x]){
dfs(y);
rt[x]=merge(rt[x],rt[y]);
}
while(sm(rt[x])>m)rt[x]=split(rt[x]);
ans=max(ans,sz(rt[x])*lead[x]);
}
}lt;
给定一个序列a,求一个递增序列b,使得sum(i=1->n)(|a[i]-b[i]|)最小。若a是一个单调不降的序列,那么可以取b[i]=a[i],若a单调不升,那么取b[i]的a的中位数为最优,于是将序列分成一些单调不升的几段,每段都取中位数,但是这些中位数组成的序列可能并不是单调不降的,所以需要合并两个区间并重新取中位数。对于求区间中位数,就是所有元素入堆,病弹出最大值知道只剩一半元素。合并区间的前提是该区间中位数比后面区间中位数大。对于每个ai,减去i并不会影响,于是可以转化成单调不降的b数列。
struct node{
int l,r,sz,rt,v;
}s[N];
for(int i=1;i<=n;i++){
int x;
cin>>x;
lt.create(x-i);
}
for(int i=1;i<=n;i++){
s[++top]={i,i,1,i,lt.t[i].v};
while(top>1&&s[top-1].v>s[top].v){
top--;
s[top].rt=lt.merge(s[top].rt,s[top+1].rt);
s[top].sz+=s[top+1].sz;
s[top].r=s[top+1].r;
while(s[top].sz>(s[top].r-s[top].l+2)/2){
s[top].sz--;
s[top].rt=lt.split(s[top].rt);
}
s[top].v=lt.t[s[top].rt].v;
}
}
for(int i=1,p=1;i<=n;i++){
p+=(i>s[p].r);
ans+=abs(s[p].v-lt.t[i].v);
}
cout<<ans<<'\n';
for(int i=1,p=1;i<=n;i++){
p+=(i>s[p].r);
cout<<s[p].v+i<<' ';
}