题意:
现在有一个高度为h的满二叉大顶堆。每个点的值都是不一样的。你要缩小这个堆到高度为g,并且这个二叉堆依旧是满的。每次你选择一个点i,执行以下操作:
然后a[x]=0表示点x已被删除。
让你构造一个i的序列使得最后这个二叉堆的所有节点的值的和最小。问你这个和以及删除的序列。
题解:
初始化的顺序错了,,一直T。
那么你构造序列的时候其实就相当于删除了当前点的值,并且从这个点到叶子结点的贪心最大路径上的值往上移一格。所以这题可以贪心做。
那么由于它是二差大顶堆,而高度最大是20,那么我们可以暴力从大到小枚举要删的值是什么。然后查看这个值是否可以删除,怎么查看,执行图中操作,找到最下面的点,查看这个点的高度是否为g,如果是的话就不能再删了。此时,这个高度为g直到根节点的这些点的值都不能在被选择。因为如果选了这些点,那么就会查到这个点。如果这个节点可以被删除,那么再从选择的值开始做一遍图中的操作即可。
siz数组好像其实并没有什么用
dep数组表示当前点的深度
pos[x]表示值x所在的位置是什么
vis数组表示当前点是否可以被选
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=5e6+5;
int a[N*2],b[N],dep[N],siz[N],n,m,pos[N],vis[N];
vector<int>vec;
int p;
int del(int x){
int now=x,f=0;
while(a[now]){
if(a[now<<1]<a[now<<1|1])
now=now<<1|1;
else
now=now<<1;
}
now>>=1;
p=now;
if(dep[now]==m&&siz[now]==1)
return 0;
while(p)
siz[p]--,p>>=1;
while(a[x]){
if(a[x<<1]<a[x<<1|1])
a[x]=a[x<<1|1],pos[a[x]]=x,x=x<<1|1;
else
a[x]=a[x<<1],pos[a[x]]=x,x=x<<1;
}
return 1;
}
int main()
{
int t;
scanf("%d",&t);
while(t--){
vec.clear();
scanf("%d%d",&n,&m);
ll sum=0;
for(int i=1;i<(1<<n);i++){
scanf("%d",&a[i]),b[i]=a[i];
sum+=a[i];
pos[a[i]]=i;
dep[i]=log2(i)+1+1e-6;
siz[i]=(1<<(n-dep[i]+1))-1;
}
sort(b+1,b+1+(1<<n)-1);
int res=1<<n;
res--;
int num=res;
while(res>=(1<<m)){
int ans=pos[b[num]];
while(!del(pos[b[num]])){
while(p)
vis[a[p]]=1,p>>=1;
num--;
while(vis[b[num]])
num--;
ans=pos[b[num]];
}
vec.push_back(ans);
sum-=b[num--];
res--;
}
printf("%lld\n",sum);
for(auto i:vec)
printf("%d ",i);
printf("\n");
for(int i=1;i<(1<<n);i++)vis[a[i]]=0,a[i]=0;
}
return 0;
}