题意
给一课n个节点的树, 给出2k个关键点, 将他们分成k对, 每一对u, v产生dis(u, v)的贡献, 求最大贡献
题解
很明显能想到贪心, 但是贪心策略并不是那么显然.
考虑每条边的贡献, 肯定是让边一端的与另一边相连最优. 那么每条边造成min(siz[v], n - siz[v])的贡献, dfs一遍即可.
还有一种方法是考虑最大siz(这里指的是包含关键点的数量). 从根开始, 对于当前u的最大siz的子树v, 如果siz[v] > u的其他子树siz之和, 那么就可以从v里向其他子树的点相连, 因为是从根开始, 所以保证当前lca肯定最优. 然后其他子树都被匹配, 我们再去把还剩一些节点的最大子树v递归处理, 肯定还剩偶数个点数. 如果siz[v] < 其他子树siz之和, 那么一定存在一种方案当前就将所有点配对. 因为只需通过这种配对抵消的方式使所有子树都互相抵消只剩相同点数, 然后互相再相互抵消完毕.
对于当前是关键点的特判即可.
#include<stdio.h>
#include<algorithm>
using namespace std;
typedef long long lnt;
const int maxn = 2e5 + 5;
lnt ans, siz[maxn];
int n, m, num, mark;
int h[maxn];
struct edge
{
int nxt, v;
}e[maxn * 2];
inline const int read()
{
register int x = 0;
register char ch = getchar();
while(ch < '0' || ch > '9') ch = getchar();
while(ch >= '0' && ch <= '9') x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
return x;
}
inline void add(int u, int v)
{
e[++num].v = v, e[num].nxt = h[u], h[u] = num;
e[++num].v = u, e[num].nxt = h[v], h[v] = num;
}
void dfs(int u, int fa)
{
for (int i = h[u]; i; i = e[i].nxt)
{
int v = e[i].v;
if(v == fa) continue;
dfs(v, u);
siz[u] += siz[v];
ans += min(siz[v], m - siz[v]);
}
}
int main()
{
n = read(), m = read();
m *= 2;
int u, v;
for (int i = 1; i <= m; ++ i) siz[u = read()] = 1;
for (int i = 1; i < n; ++ i)
{
u = read(), v = read();
add(u, v);
}
dfs(1, 1);
printf("%lld\n", ans);
}