Step1 Problem:
给出一颗n个节点树,让你删除k个节点,使其还是一棵树,并且要求Σ(2^i)最大,i是剩下的节点的编号。题意参考
数据范围:
1 <= k < n <= 1e6.
Step2 Ideas:
显而易见,剩下的节点编号越大越好。
从编号大到小选择,
到第 i 节点 如果选择后 构成树的节点数量 没超n-k,则选择。
否则 到第 i-1 编号。
核心:需要解决选择 i 节点,快速判断构成树的节点数量 是否超n-k。
以 n 节点为根,已经选择的点标记一下,就是求节点 i 到 已经选择过的点(祖先)的最短距离 + 目前选择了多少个点 <= n - k. 那么就可以选择。
节点 i 到 已经选择过的点(祖先)的最短距离, 正常方法O(n), 如果我们用倍增预处理,那么O(logn)可以求出。
Step3 Code:
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6+100;
vector<int> Map[N];
int dep[N], up[N][22], vis[N];
void dfs(int u, int f)//倍增预处理出 当前点 2次幂距离后的祖先节点。
{
up[u][0] = f;
for(int i = 1; i < 22; i++)
{
up[u][i] = up[up[u][i-1]][i-1];
}
for(int i = 0; i < Map[u].size(); i++)
{
int to = Map[u][i];
if(to != f) dfs(to, u);
}
}
int main()
{
memset(vis, 0, sizeof(vis));
int n, k, u, v;
scanf("%d %d", &n, &k);
for(int i = 1; i < n; i++)
{
scanf("%d %d", &u, &v);
Map[u].push_back(v);
Map[v].push_back(u);
}
dfs(n, n);
vis[n] = 1;//k >= 1, 所以第一个点肯定选择n
int tmp = n-k-1;
for(int i = n-1; i >= 1 && tmp; i--)
{
if(vis[i]) continue;
u = i; int len = 0;
for(int j = 20; j >= 0; j--)//求出 i 节点到 标记过祖先的距离
{
if(!vis[up[u][j]]) {
len += 1<<j;
u = up[u][j];
}
}
len++;//算上自身
// printf("%d %d %d\n", i, tmp, len);
if(len <= tmp) { // 数量上满足
tmp -= len;
u = i; len--; vis[u] = 1;
while(len) { // 沿路上的点标记一下
vis[up[u][0]] = 1;
u = up[u][0];
len--;
}
}
}
int flag = 0;
for(int i = 1; i <= n; i++)
{
if(!vis[i]) {
if(flag) printf(" ");
printf("%d", i);
flag++;
}
}
printf("\n");
return 0;
}