codeforces 592D(树DP)

题意是要经过一棵树上的一些点(把这些点叫m点吧),可以从任意的点出发,问最少走几次边。

可以预先删除无关的节点,比如从节点1开始dfs,当某个节点为根子树中不含有m点的情况下可以删除这棵子树。然后就得到了一棵新的树,这棵树每一个叶子节点必然是m点,但是原本设定的根节点1应该有所改变。

然后以一种巨搓无比的姿势水过,各种树DP瞎搞。

最优的情况显然是走最大直径,最大直径外的边每条走两次。

至于找最小的起点可以这么搞,对于每个在最大直径上的点都标记下(注意可能有很多最大直径),然后所有标记的点中度数最小的就是起点。

#include <bits/stdc++.h>
using namespace std;
#define maxn 133333
#define maxm 266666
#define INF 11111111

int n, m;
struct node {
    int from, to, next;
}edge[maxm], edge1[maxm];
int head[maxn], head1[maxn], is[maxn]; //是不是m点
int cnt, cnt1, degree[maxn]; //判断叶子节点
int num[maxn]; //以i为根的子树含有m点的个数
bool vis[maxn]; //以i为根的子树是不是删掉了
int start;

void add_edge (int from, int to, int i) {
    node &e = edge[i];
    e.from = from, e.to = to, e.next = head[from], head[from] = i;
}
void add_edge1 (int from, int to, int i) {
    node &e = edge1[i];
    e.from = from, e.to = to, e.next = head1[from], head1[from] = i;
}

void get_num (int u, int fa) {
    if (is[u])
        num[u]++;
    for (int i = head[u]; i != -1; i = edge[i].next) {
        int v = edge[i].to;
        if (v == fa)
            continue;
        get_num (v, u);
        num[u] += num[v];
    }
    return ;
}

void del (int u, int fa) {
    if (!num[u]) {
        vis[u] = 1;
        return ;
    }
    for (int i = head[u]; i != -1; i = edge[i].next) {
        int v = edge[i].to;
        if (v == fa)
            continue;
        del (v, u);
    }
}

int find_root (int u, int fa) {
    for (int i = head[u]; i != -1; i = edge[i].next) {
        int v = edge[i].to;
        if (v == fa || vis[v])
            continue;
        if (num[u] == num[v]) {
            vis[u] = 1;
            return find_root (v, u);
        }
        else return u;
    }
    return u;
}

void rebuild (int u, int fa) {
    for (int i = head[u]; i != -1; i = edge[i].next) {
        int v = edge[i].to;
        if (vis[v] || v == fa)
            continue;
        add_edge1 (u, v, cnt1++);
        add_edge1 (v, u, cnt1++);
        degree[u]++;
        degree[v]++;
        rebuild (v, u);
    }
}

int Max;
int ans[maxn][2]; //从i子树向下的最长 次长距离
int son[maxn][2]; //最长 次长的节点
int up[maxn]; //往上走的最大长度
void dfs (int u, int fa) {
    for (int i = head1[u]; i != -1; i = edge1[i].next) {
        int v = edge1[i].to;
        if (vis[v] || v == fa)
            continue;
        dfs (v, u);
        if (ans[v][0]+1 > ans[u][0]) {
            ans[u][1] = ans[u][0];
            ans[u][0] = ans[v][0]+1;
            son[u][1] = son[u][0];
            son[u][0] = v;
        }
        else if (ans[v][0]+1 > ans[u][1]) {
            ans[u][1] = ans[v][0]+1;
            son[u][1] = v;
        }
    }
    Max = max (Max, ans[u][0]+ans[u][1]);
}

bool mark[maxn]; //是不是最大直径上面的点
void find_start (int u, int fa) {
    if (ans[u][0]+ans[u][1] == Max) {
        mark[u] = 1;
    }
    else if (fa) {
        if (up[u]+ans[u][0] == Max)
            mark[u] = 1;
    }
    for (int i = head1[u]; i != -1; i = edge1[i].next) {
        int v = edge1[i].to;
        if (v == fa || vis[v])
            continue;
        if (v == son[u][0]) {
            up[v] = max (up[u], ans[u][1])+1;
        }
        else up[v] = max (up[u], ans[u][0])+1;
        find_start (v, u);
    }
}

int main () {
    //freopen ("in", "r", stdin);
    scanf ("%d%d", &n, &m);
    memset (head, -1, sizeof head);
    memset (is, 0, sizeof is);
    cnt = 0;
    int u, v;
    for (int i = 1; i < n; i++) {
        scanf ("%d%d", &u, &v);
        add_edge (u, v, cnt++);
        add_edge (v, u, cnt++);
    }
    memset (num, 0, sizeof num);
    for (int i = 1; i <= m; i++) {
        scanf ("%d", &u);
        is[u] = 1;
    }
    get_num (1, 0);
    memset (vis, 0, sizeof vis);
    del (1, 0);
    int root = find_root (1, 0); //重新确定根
    memset (head1, -1, sizeof head1);
    memset (degree, 0, sizeof degree);
    memset (mark, 0, sizeof mark);
    cnt1 = 0;
    rebuild (root, 0); //新建一棵树
    cnt1 >>= 1; //新树的边
    Max = 0;
    memset (ans, 0, sizeof ans);
    dfs (root, 0); //最长直径
    find_start (root, 0);
    for (int i = 1; i <= n; i++) {
        if (mark[i] && (degree[i] == 1|| degree[i] == 0)) {
            printf ("%d\n", i);
            break;
        }
    }
    printf ("%d\n", Max+(cnt1-Max)*2);
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值