题意:
给一棵n个节点的树,节点i的权值为2i
现在要求去掉k个点,使得在保证剩下的点连通的情况下点权和最大
输出删点方案
数据范围:k<=n<=1e6
解法:
发现每个节点都是二进制位的某一位
要使得剩下的点权值最大,显然要尽量保留高位
考虑将问题转化为保留n-k位,优先保留高位
那么点n肯定保留,因为最大,不妨令n为根节点
接下来从大到小枚举点,判断能否保留
一个点被保留,显然这个点到根节点的上未被保留的所有点都要被保留
需要保留的总点数为路径上未被保留的点的数量,用数量判断一下能否保留即可
判断某个点到根节点路径上未被保留的点的数量可以用这个点的深度-路径上已经被保留的数量
考虑直接记录每个点到根节点路径上已被保留点的数量,一开始为0
当一个点被保留的时候,显然以这个点为根的子树所有点都+1
子树操作可以用dfs序+线段树实现
标记某个点需要标记这个点到根路径上的所有点,这个可以直接暴力上移
总复杂度O(nlogn)
ps:
判断路径上未被保留的点的数量还有一种思路:
二分到达已标记的点距离mid,然后向上爬mid看是否被标记,向上爬会想到倍增
但是既然用了倍增,可以直接用倍增数组向上爬,不用二分
因此这题也可以利用倍增数组不断上爬计算出到达已标记点的距离
code:
#include<bits/stdc++.h>
using namespace std;
const int maxm=1e6+5;
vector<int>g[maxm];
int L[maxm],R[maxm],rk[maxm],idx;
int pre[maxm],d[maxm];
int mark[maxm];
int n,k;
void dfs(int x,int fa){
L[x]=++idx;
rk[idx]=x;
for(int v:g[x]){
if(v==fa)continue;
d[v]=d[x]+1;
pre[v]=x;
dfs(v,x);
}
R[x]=idx;
}
struct Tree{
int a[maxm<<2];
void pushdown(int node){
if(a[node]){
a[node*2]+=a[node];
a[node*2+1]+=a[node];
a[node]=0;
}
}
void update(int st,int ed,int l,int r,int node){
if(st<=l&&ed>=r){
a[node]++;
return ;
}
pushdown(node);
int mid=(l+r)/2;
if(st<=mid)update(st,ed,l,mid,node*2);
if(ed>mid)update(st,ed,mid+1,r,node*2+1);
}
int ask(int x,int l,int r,int node){
if(l==r)return a[node];
pushdown(node);
int mid=(l+r)/2;
if(x<=mid)return ask(x,l,mid,node*2);
else return ask(x,mid+1,r,node*2+1);
}
}T;
signed main(){
scanf("%d%d",&n,&k);
for(int i=1;i<n;i++){
int a,b;scanf("%d%d",&a,&b);
g[a].push_back(b);
g[b].push_back(a);
}
pre[n]=n;
d[n]=1;
dfs(n,-1);
k=n-k;
for(int i=n;i>=1;i--){
if(mark[i])continue;
int cnt=d[i]-T.ask(L[i],1,n,1);//计算路径上未标记的节点数量
if(cnt>k)continue;
k-=cnt;
int now=i;
while(!mark[now]){
mark[now]=1;
T.update(L[now],R[now],1,n,1);
now=pre[now];
}
}
for(int i=1;i<=n;i++){
if(!mark[i]){
printf("%d ",i);
}
}
return 0;
}