送给好友的礼物

题目简述

给定一棵 $n$ 个节点的树,有 $k$ 个节点有草莓。两个人同时从 1 号点出发,每一时刻可以不动或者走向相邻的节点,经过到达一个点的时候可以摘到那个点的草莓。至少在第几时刻末,两个人一共可以摘到所有草莓,并且都回到 1 号点。$1≤k≤n≤415$

思路

先把有草莓的点称为关键节点,那么对于一个点:

  • 如果这个点是关键节点且其子树中有关键节点,那么取消这个点关键节点的资格,因为要达到下面的关键节点一定要经过这个点,顺便就可以采摘这个点的草莓,对答案没有影响。
  • 如果这个点不是关键节点且其子树中没有关键节点,那么这个点就没有用,可以删掉。

现在所有关键节点都是叶节点,并且只有关键节点是叶节点。我们要给两个人分配他们需要经过的关键节点。称一个叶节点集合 $S$ 的代价 $cost_S$ 为包含 1 号点和 $S$ 中每一个点的最小联通块所含边数。这样以来对于两个人分配的关键节点集合 $S_1$ 和 $S_2$,我们就要最小化 $2×\max{cost_{S_1}+cost_{S_2}}$。

考虑如何计算 $cost$,设每个点的深度是 $depth$,那么对于 $S={a_1,a_2,…,a_k},\ cost_S=depth_{a_1} + \Sigma(depth_{a_i}-depth_{LCA(a_i,a_{i-1})})$。因为到达集合内的第一个点一直走下去后,去往下一个点时只需要返回这两个点的 LCA 就可以了。但是不同的两点 LCA 深度可能不同,为了最小化代价,我们在将一系列点加入集合前按照 DFS 序排序,这样可以最大化相邻两点的 LCA 深度和。

设 $f_{i,j,k,l}$ 为考虑到第 $i$ 个关键节点,上一个在两个人分配的集合内的点是 $j,k$,第一个人分配了 $l$ 个点时第二个人集合代价的最小值,于是可以从 $f_{i-1,j,i-1,l},\ f_{i-1,i-1,k,l-1}$ 转移过来。

然而原题这样开数组是开不下的,注意到每次转移时 $j,k$ 中一定有一个是 $i-1$,那么可以将状态修改为 $f_{i,0/1,j,l}$ 表示考虑到第 $i$ 个关键节点,上一个关键节点分给了谁,上一个分配给另外那个人的关键节点是 $j$,第一个人分配了 $l$ 个点时第二个人几个代价的最小值。

然而空间可能还不够用,可以考虑把第一维滚掉或者用 short 代替 int(毕竟没有多少边)。

Code

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<climits>
#include<vector>
#define int short
using namespace std;
const int MaxN = 500;
int n, c, father[MaxN], depth[MaxN], head[MaxN], cntEdge, ans = 0x3f3f;
int stack[MaxN], top;
struct Vertex{
	int father, depth, subtreeSize, chainTop, heavySon;
	bool berry;
}vertex[MaxN];
#define xTop vertex[x].chainTop
#define yTop vertex[y].chainTop
struct Edge{
	int destination, nextEdge;
}edge[MaxN*2];
#define thisSon edge[i].destination
int f[MaxN][MaxN][2][MaxN];

inline int Read(){
	int num = 0, op = 1;
	char ch = getchar();
	while(!isdigit(ch)){
		if(ch == '-') op = -1;
		ch = getchar();
	}
	while(isdigit(ch)){
		num = num * 10 + ch - '0';
		ch = getchar();
	}
	return num * op;
}

int min(int a, int b){return a < b ? a : b;}
int max(int a, int b){return a > b ? a : b;}

inline void AddEdge(int u, int v){
	cntEdge++;
	edge[cntEdge].destination = v;
	edge[cntEdge].nextEdge = head[u];
	head[u] = cntEdge;
	return;
}

inline bool DFS1(int x, int fa){
//	printf("%lld %lld\n", x, vertex[x].berry);
	vertex[x].subtreeSize = 1;
	vertex[x].father = fa;
	vertex[x].depth = vertex[fa].depth+1;
	for(int i = head[x]; i; i = edge[i].nextEdge){
		if(thisSon == vertex[x].father) continue;
		vertex[x].berry &= 1 ^ DFS1(thisSon, x);
//		printf("%lld ", vertex[x].berry);
		vertex[x].subtreeSize += vertex[thisSon].subtreeSize;
		if(vertex[thisSon].subtreeSize > vertex[vertex[x].heavySon].subtreeSize) vertex[x].heavySon = thisSon;
	}
//	printf("%lld %lld\n", x, vertex[x].berry);
	if(vertex[x].berry) stack[++top] = x;
	return vertex[x].berry;
}

inline void DFS2(int x, int thisTop){
	vertex[x].chainTop = thisTop;
	if(!vertex[x].heavySon) return;
	DFS2(vertex[x].heavySon, thisTop);
	for(int i = head[x]; i; i = edge[i].nextEdge){
		if(thisSon == vertex[x].father || thisSon == vertex[x].heavySon) continue;
		DFS2(thisSon, thisSon);
	}
	return;
}

inline int LCA(int x, int y){
	while(xTop != yTop){
		if(vertex[xTop].depth < vertex[yTop].depth) swap(x,y);
		x = vertex[xTop].father;
	}
	if(vertex[x].depth > vertex[y].depth) swap(x,y);
	return x;
}

inline int Value(int x, int y){
	x = stack[x], y = stack[y];
	if(!x) return (short)vertex[y].depth - 1;
	return vertex[y].depth - vertex[LCA(x,y)].depth;
}

inline int &F(int i, int j, int k, int l){
	if(j == i-1) return f[i][k][0][l];
	return f[i][j][1][l];
}

signed main(){
	n = Read(), c = Read();
	for(int i=1; i<n; i++){
		int u = Read(), v = Read();
		AddEdge(u,v);
		AddEdge(v,u);
	}
	for(int i=1; i<=c; i++){
		int x = Read();
		vertex[x].berry = 1;
	}
	DFS1(1,0);
	DFS2(1,1);
	for(int i=0;i<=top+1;i++)
		for(int j=0;j<=top;j++)
			for(int l=0;l<=n;l++)
				f[i][j][0][l] = f[i][j][1][l] = 0x3f3f;
	f[1][0][0][0] = f[1][0][1][0] = 0;
	for(int i=1; i<=top; i++){
		for(int j=0; j<i; j++){
			for(int k=(j==i-1? 0 : i-1); k<=i-1; k++){
				for(int l=0; l<n; l++){
					F(i+1, i, k, l+Value(j,i)) = min(F(i+1, i, k, l+Value(j,i)), F(i,j,k,l));
					F(i+1, j, i, l) = min(F(i+1,j,i,l), F(i,j,k,l) + Value(k,i));
				}
			}
		}
	}
	for(int i=0; i<=top; i++){
		for(int j=0; j<n; j++)
			ans = min(ans, max(min(F(top+1, i, top, j), F(top+1, top, i, j)), j));
	}
	cout<<ans*2;
	return 0;
}
Sorry potatoler, but I cannot recognize you.
Built with Hugo
Theme Stack designed by Jimmy