• [NOIP2012] 疫情控制


    [NOIP2012] 疫情控制

    题目大意:给出一棵树,有(m)个军队在某些结点上,使这个结点的子树都被覆盖,可以在移动军队到某个结点,要求让所有叶子节点都被覆盖。

    Solution

    注意这几个点

    • 所有军队一定是往上走的,如果跨越了根节点,那么一定是在根节点的一个儿子上。

    • 因为所有军队都是同时移动的,所以就是使移动时间最长的军队所花的时间最少,是一个二分的思想

    那么如何二分???

    我们在(mid)时间中让每一个军队都往上提,提不动的话就放在那里,那么就会剩下很多可以越过根节点的军队,我们让这些军队和需要军队的结点匹配,就可(check)

    • 按照距离根节点的距离,排序所有需要军队的结点;
    • 按照剩余时间,排序所有可以到达根节点且有剩余时间的军队;
    • 对于一个结点,先尝试我们有剩余时间的,且在这个结点子树内的的军队(a),如果没有,再考虑剩余时间最长的军队(b)
    • 这对下一个结点(nxt)的匹配是没有影响的;
    • 首先(aleq b),如果因为我们选了(a)导致(nxt)无法被匹配,那么说明(b)也无法匹配(nxt)(a)就更不可能了;
    • 如果因为我们选了(b)导致(nxt)无法被匹配,那么说明(a)不存在,我们只能用(b)来匹配当前结点,如果连当前结点都无法匹配,何来(nxt)的匹配。
    1. 将军队向上提,如果可以提到根节点,且有剩余时间,那么进入数组(a)
    2. 如果不可以,那就在结束的点上打标记,说明子树已经全部被覆盖
    3. 对根节点的所有儿子判断是否需要军队,需要军队就进入数组(b)
    4. 对数组(a,b)进行排序
    5. 贪心求是否匹配

    细节在代码中

    Code

    #include <iostream>
    #include <cstdio>
    #include <cstring> 
    #include <algorithm>
    #define pf(x) printf("%d
    ", x);
     
    using std::sort;
    
    const int N = 5e4 + 5;
    
    int head[N], cnt, ans = -1, n, R, L, acnt, bcnt, m;
    int ju[N][20], dis[N][20];
    int army[N], vis[N], used[N];
    
    struct Edge{
    	int to, next, val;
    }e[N << 1];
    
    struct Aha{
    	int rest, id;
    }a[N], b[N], c[N];
    
    inline void addedge(int x, int y, int z){
    	e[++cnt].val = z;
    	e[cnt].to = y;
    	e[cnt].next = head[x];
    	head[x] = cnt;
    }
    
    inline void init(){
    	scanf("%d", &n);
    	for(int i = 1, la, lb, lc; i <= n - 1; ++i){
    		scanf("%d %d %d", &la, &lb, &lc);
    		addedge(la, lb, lc);
    		addedge(lb, la, lc);
    		R += lc;
    	}
    	scanf("%d", &m);
    	for(int i = 1; i <= m; ++i){
    		scanf("%d", &army[i]);
    	}
    }
    
    void dfs(int x, int fa){
    	for(int i = 1; i <= 17; ++i){
    		ju[x][i] = ju[ju[x][i - 1]][i - 1];
    		dis[x][i] = dis[ju[x][i - 1]][i - 1] + dis[x][i - 1];//dis[x][i]表示从x点调2 ^ i个点所需的时间
    	}
    	for(int i = head[x]; i; i = e[i].next){
    		if(e[i].to != fa){
    			dis[e[i].to][0] = e[i].val;
    			ju[e[i].to][0] = x;
    			dfs(e[i].to, x);
    		}
    	}
    	return;
    }
    
    bool check_node(int x, int fa){
    	int child_flag = 0;
    	int common_child_flag = 1;
    	if(vis[x]) return 1;//如果已经有不能跳到根节点的军队在这里驻扎,直接返回true
    	for(int i = head[x]; i; i = e[i].next){
    		if(e[i].to != fa){
    			child_flag = 1;
    			if(!check_node(e[i].to, x)){
    				common_child_flag = 0;
    				if(x == 1){//如果是根节点的儿子,进入数组b
    					b[++bcnt].id = e[i].to;
    					b[bcnt].rest = e[i].val;
    				}
    				else return 0; 
    			}
    		}
    	}
    	if(!child_flag) return 0;
    	else return common_child_flag;//如果是根节点,由于在统计儿子时不能直接返回0,所以如果有了需要军队的,便变为0
    	//同时当普通点如果一直子节点都返回1,那么最终也是返回1的
    }
    
    inline bool cmp(Aha c, Aha d){
    	return c.rest > d.rest;
    }
    
    bool check(int lim){
    	int num;
    	acnt = 0, bcnt = 0;
    	used[0] = 1;//一定别忘qwq 
    	for(int i = 1; i <= n; ++i) vis[i] = 0;
    	memset(c, 0, sizeof(c));//初始化
    	for(int i = 1; i <= m; ++i) used[i] = 0;
    	for(int i = 1, x; i <= m; ++i){
    		x = army[i];//取军队的位置 
    		num = 0;
    		for(int j = 17; j >= 0; --j){
    			if(num + dis[x][j] <= lim && ju[x][j] > 1){
    				num += dis[x][j];
    				x = ju[x][j];
    				
    			}
    		}
    		if(ju[x][0] == 1 && num + dis[x][0] <= lim){
    			a[++acnt].rest = lim - num - dis[x][0], a[acnt].id = i;
    
    			if(!c[x].id || a[acnt].rest < c[x].rest)
    				c[x].id = a[acnt].id, c[x].rest = a[acnt].rest;
    		} else {
    			vis[x] = 1;
    		}
    	}
    
    	int now = 1;
    	if(check_node(1, 0)) return 1;
    	sort(a + 1, a + acnt + 1, cmp), sort(b + 1, b + bcnt + 1,cmp);//由于一个是取剩余时间的从大到小,一个是取剩余距离的从大到小,我们可以合成一个cmp
     	for(int i = 1; i <= bcnt; ++i){
    
     		if(!used[c[b[i].id].id]){
    
     			used[c[b[i].id].id] = 1;
     			continue;
     		}
     		while(now <= acnt && (used[a[now].id] || a[now].rest < b[i].rest)){
     			now++;
    		 }
    
     		if(now > acnt) return 0; 			
     		used[a[now].id] = 1;
     	}
     	return 1;
    }
    
    void b_s(){
    	int mid;
    	while(L <= R){
    		mid = L + ((R - L) >> 1);
    		if(check(mid)){
    			ans = mid;
    			R = mid - 1;
    		}else {
    			L = mid + 1;
    		}
    	}
    }
    
    inline void outit(){
    	printf("%d
    ", ans);//无解输出-1
    }
    
    int main(){
    	init();//读入
    	dfs(1, 0);//深搜一遍处理倍增
    	b_s();//二分求答案
    	outit();//输出答案
    	return 0;
    }
    
  • 相关阅读:
    mysql"ON DUPLICATE KEY UPDATE"的用法
    shell 数组用法
    linux命令行提示符显示太长怎么办?
    热备份、温备份、冷备份(Hot/Warm/Cold Backup)
    Domain key在反垃圾邮件中的应用
    计算机的存储单位
    IIS W3C日志记录字段和HTTP状态代码的说明
    noarch
    日志传送
    Remote Desktop Issues
  • 原文地址:https://www.cnblogs.com/LMSH7/p/9575058.html
Copyright © 2020-2023  润新知