CF1442E Black, White and Grey Tree

一棵树,有每个点有黑、白、灰三种颜色。一次操作可以选择在同一连通块中的若干个点,不能同时包含黑点和白点,将其删除。

问删完整棵树最少的操作次数。

\(n\le 2*10^5\)


考虑假如只有黑点和白点,并且分布于一条链上该怎么做:

首先相邻颜色相同的显然可以缩点,于是变成了一串交替的黑白序列。设长度为\(n\),则可以构造出\(\lfloor\frac{n}{2}\rfloor+1\)的解法,并且可以证明这个一定最优。

归纳:设长度为\(n\)的链的答案为\(f_n\),假如一次删除删了\(k\)个点,那么有\(f_n\leftarrow 1+\sum_{\sum a_i=n-k,|a|\ge k-1}f_{a_i}=1+\sum_{\sum a_i=n-k,|a|\ge k-1}\lfloor\frac{a_i}{2}\rfloor+1\ge 1+\sum_{\sum a_i=n-k,|a|\ge k-1}\frac{a_i+1}{2}\ge \frac{n+1}{2}\),如果存在更优的方法,则\(\lfloor\frac{n}{2}\rfloor+1> \frac{n+1}{2}\)为必要条件,如果\(n\)为奇数则取等号,如果\(n\)为偶数,发现上式中\(|a|\)不可能取到\(k-1\),因为左右端点颜色不同。因此得到最优解为\(\lfloor\frac{n}{2}\rfloor+1\)

达到这个最优解的方法不止一种,直接跟正解关联的是:每次选择直径的一个端点,删去这个端点,如果另一个端点同色就一起删去。

扩展到树上只有黑白点的情况。同样先缩点,然后找直径。答案一定不小于\(\lfloor\frac{len}{2}\rfloor+1\)\(len\)为直径长度。

将上面的方法扩展一下:每次选择直径的端点,删去与端点同色的所有叶子节点。

可以证明,这样操作的过程中直径始终不会变:反证,假设出现了新直径。如果\(len\)为偶数,新直径的长度为\(len\),它的端点颜色不同,至少有一个会在操作中删去;如果\(len\)为奇数,新直径的长度为\(len\)\(len-1\)和上面类似),它端点的颜色和原直径相反,所以两条直径的中点颜色不同。因为直径的中点只有一个(否则可以调整将这个直径的中点替换掉),所以不合法。

按照这个方法做,整棵树的操作次数和直径的操作次数是一样的,压到了下界\(\lfloor\frac{len}{2}\rfloor+1\)

实现的时候有个更加舒服的方法:显然以上面这个方法做的时候,黑白轮流交替删除叶子结点。所以枚举先删哪个颜色,然后交替删即可。

现在增加了灰点。首先明确:设树上最长的黑白交替的子序列长度\(len\),下界为\(\lfloor\frac{len}{2}\rfloor+1\)。找到这个子序列的一个中点,以它作为根,所有的灰点都变成父亲的颜色,再套用前面的做法。可以发现仍然可以压到下界\(\lfloor\frac{len}{2}\rfloor+1\)

实现的时候,枚举先删哪个颜色,然后交替删,唯一的区别在于存在灰色叶子结点不管是哪个颜色都可以直接删。


using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#define N 200005
int n;
int a[N];
struct EDGE{
	int to;
	EDGE *las;
} e[N*2];
int ne;
EDGE *last[N];
int d[N];
queue<int> q[3];
int work(int c){
	memset(d,0,sizeof(int)*(n+1));
	for (int i=1;i<=n;++i)
		for (EDGE *ei=last[i];ei;ei=ei->las)
			d[i]++;
	for (int i=0;i<3;++i)
		while (!q[i].empty())
			q[i].pop();
	for (int i=1;i<=n;++i)
		if (d[i]==1)
			q[a[i]].push(i);
	int res=0;
	while (!q[c].empty() || !q[0].empty()){
		res++;
		while (!q[c].empty() || !q[0].empty()){
			if (!q[c].empty()){
				int x=q[c].front();
				q[c].pop();
				for (EDGE *ei=last[x];ei;ei=ei->las)
					if (--d[ei->to]==1)
						q[a[ei->to]].push(ei->to);
			}
			else{
				int x=q[0].front();
				q[0].pop();
				for (EDGE *ei=last[x];ei;ei=ei->las)
					if (--d[ei->to]==1)
						q[a[ei->to]].push(ei->to);
			}
		}
		c=3-c;
	}
	return q[0].empty() && q[1].empty() && q[2].empty()?res:n;
}
int main(){
//	freopen("in.txt","r",stdin);
	int T;
	scanf("%d",&T);
	while (T--){
		scanf("%d",&n);
		for (int i=1;i<=n;++i)
			scanf("%d",&a[i]);
		if (n==1){
			printf("1\n");	
			continue;
		}
		memset(last,0,sizeof(EDGE*)*(n+1));
		ne=0;
		for (int i=1;i<n;++i){
			int u,v;
			scanf("%d%d",&u,&v);
			e[ne]={v,last[u]};
			last[u]=e+ne++;
			e[ne]={u,last[v]};
			last[v]=e+ne++;
		}
		printf("%d\n",min(work(1),work(2)));
	}
	return 0;
}
posted @ 2020-11-06 07:32  jz_597  阅读(125)  评论(0编辑  收藏  举报