zoukankan      html  css  js  c++  java
  • CF1442E Black, White and Grey Tree

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

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

    (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;
    }
    
  • 相关阅读:
    Android客户端消息推送原理简介
    优秀程序员的十个习惯
    能让你成为更优秀程序员的10个C语言资源
    成大事必备9种能力、9种手段、9种心态
    33个优秀的HTML5应用演示 (转)
    Maven学习:Eclipse使用maven构建web项目(转)
    使用fiddler模拟http get
    TLS握手
    风暴英雄 http 302重定向 正在等待游戏模式下载完成
    page template in kentico
  • 原文地址:https://www.cnblogs.com/jz-597/p/13934936.html
Copyright © 2011-2022 走看看