codeforces 592D(树DP)
2015-11-02 22:48
387 查看
题意是要经过一棵树上的一些点(把这些点叫m点吧),可以从任意的点出发,问最少走几次边。
可以预先删除无关的节点,比如从节点1开始dfs,当某个节点为根子树中不含有m点的情况下可以删除这棵子树。然后就得到了一棵新的树,这棵树每一个叶子节点必然是m点,但是原本设定的根节点1应该有所改变。
然后以一种巨搓无比的姿势水过,各种树DP瞎搞。
最优的情况显然是走最大直径,最大直径外的边每条走两次。
至于找最小的起点可以这么搞,对于每个在最大直径上的点都标记下(注意可能有很多最大直径),然后所有标记的点中度数最小的就是起点。
可以预先删除无关的节点,比如从节点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; }