一棵树,有每个点有黑、白、灰三种颜色。一次操作可以选择在同一连通块中的若干个点,不能同时包含黑点和白点,将其删除。
问删完整棵树最少的操作次数。
(nle 2*10^5)
考虑假如只有黑点和白点,并且分布于一条链上该怎么做:
首先相邻颜色相同的显然可以缩点,于是变成了一串交替的黑白序列。设长度为(n),则可以构造出(lfloorfrac{n}{2} floor+1)的解法,并且可以证明这个一定最优。
归纳:设长度为(n)的链的答案为(f_n),假如一次删除删了(k)个点,那么有(f_nleftarrow 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}lfloorfrac{a_i}{2} floor+1ge 1+sum_{sum a_i=n-k,|a|ge k-1}frac{a_i+1}{2}ge frac{n+1}{2}),如果存在更优的方法,则(lfloorfrac{n}{2} floor+1> frac{n+1}{2})为必要条件,如果(n)为奇数则取等号,如果(n)为偶数,发现上式中(|a|)不可能取到(k-1),因为左右端点颜色不同。因此得到最优解为(lfloorfrac{n}{2} floor+1)。
达到这个最优解的方法不止一种,直接跟正解关联的是:每次选择直径的一个端点,删去这个端点,如果另一个端点同色就一起删去。
扩展到树上只有黑白点的情况。同样先缩点,然后找直径。答案一定不小于(lfloorfrac{len}{2} floor+1),(len)为直径长度。
将上面的方法扩展一下:每次选择直径的端点,删去与端点同色的所有叶子节点。
可以证明,这样操作的过程中直径始终不会变:反证,假设出现了新直径。如果(len)为偶数,新直径的长度为(len),它的端点颜色不同,至少有一个会在操作中删去;如果(len)为奇数,新直径的长度为(len)((len-1)和上面类似),它端点的颜色和原直径相反,所以两条直径的中点颜色不同。因为直径的中点只有一个(否则可以调整将这个直径的中点替换掉),所以不合法。
按照这个方法做,整棵树的操作次数和直径的操作次数是一样的,压到了下界(lfloorfrac{len}{2} floor+1)。
实现的时候有个更加舒服的方法:显然以上面这个方法做的时候,黑白轮流交替删除叶子结点。所以枚举先删哪个颜色,然后交替删即可。
现在增加了灰点。首先明确:设树上最长的黑白交替的子序列长度(len),下界为(lfloorfrac{len}{2} floor+1)。找到这个子序列的一个中点,以它作为根,所有的灰点都变成父亲的颜色,再套用前面的做法。可以发现仍然可以压到下界(lfloorfrac{len}{2} floor+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
");
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
",min(work(1),work(2)));
}
return 0;
}