题意:
给出一棵树,要求去掉k个点,使得剩下的还是一棵树,并且要求Σ(2^i)最大,i是剩下的节点的编号。
思路:
要使得剩下的点的2的幂的和最大,那么肯定要保住大的点,这是贪心。
考虑去掉哪些点的话,那么去掉一个点,它相连的子树的点肯定都得去掉,很麻烦。
所以放过来考虑保留哪些点,那么就从大到小考虑是否保留当前的点。并且把保留的点做标记。
首先n这个点是肯定可以保留的,标记,然后依次考虑n-1,n-2。。。1。
对于当前的点,判断是否已经被标记,如果没有被标记,那么就找到离当前点最远的没有被标记的祖先,如果要标记这个点,那么总共增加的点就是从当前点到离当前点最远的没有被标记的祖先的路径上的所有点的数量,也可以解释为当前点到已经保留的点所构成的树的路径上的点。
找这个路径的时候,暴力的话,n^2,肯定会T,所以可以首先用倍增预处理,再来找祖先,复杂度就可以下降为O(nlogn)。
如果这个数量没有超过要求,那么就把路径上的点全部标记。
最后没有标记的点就是要抛弃的点。
代码:
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <vector>
using namespace std;
const int N = 1e6 + ;
int n,k;
vector<int> g[N];
int fa[N];
int dep[N];
bool vis[N];
int anc[N][];
void dfs(int u,int f)
{
fa[u] = f;
for (int v : g[u])
{
if (v != f)
{
dep[v] = dep[u] + ;
dfs(v,u);
}
}
}
void rdfs(int u,int f)
{
vis[u] = ;
if (u == f) return;
rdfs(fa[u],f);
}
void preprocess()
{
for (int i = ;i < n;i++)
{
anc[i][] = fa[i];
for (int j = ;(<<j) < n;j++) anc[i][j] = -;
}
for (int j = ;(<<j) < n;j++)
{
for (int i = ;i < n;i++)
{
if (anc[i][j-] != -)
{
int a = anc[i][j-];
anc[i][j] = anc[a][j-];
}
}
}
}
int main()
{
memset(anc,-,sizeof(anc));
scanf("%d%d",&n,&k);
k = n - k;
for (int i = ;i < n;i++)
{
int x,y;
scanf("%d%d",&x,&y);
x--,y--;
g[x].push_back(y);
g[y].push_back(x);
}
dfs(n-,-);
preprocess();
int num = ;
vis[n-] = ;
for (int i = n - ;i >= ;i--)
{
if (vis[i]) continue;
int t = i;
for (int j = ;j >= ;j--)//j是从大到小枚举
{
if (anc[t][j] == -) continue;
else
{
if (!vis[anc[t][j]])
{
t = anc[t][j];
}
}
}
if (dep[i] - dep[t] + + num <= k)
{
num += dep[i] - dep[t] + ;
rdfs(i,t);
}
}
for (int i = ;i < n;i++)
{
if (!vis[i]) printf("%d ",i+);
}
return ;
}