【树 && 倍增求到祖先距离】Codeforces Round #480 (Div. 2) E. The Number Games

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;
}

猜你喜欢

转载自blog.csdn.net/bbbbswbq/article/details/80542833