• [POI2011][树上贪心] DYN-Dynamite


    题面

    一道比较巧妙的树上贪心题,做法同Luogu P3942 将军令
    [HNOI2003]消防局的设立类似。

    首先分析题面不难想到二分答案,然后我们维护两个量:
    1. 当前子树下最远的需要覆盖(有炸弹)的点,记作 (disCo_x)
    2. 当前子树外最近的被选择引燃的点,记作 (disSel_x)

    将所有点分为三种情况考虑:
    1. 自身就是等待覆盖的关键节点(炸弹),即 (explo_x = True)(disSel_x gt val),此时 (disCo_x) 一定为 (0) (子树无炸弹)或原先值,
    2. 子树中所有的炸弹被子树外的点覆盖,即 (disSel_x + disCo_x le val) (或 (disCo_x <= val - disSel_x)),此时将子树未覆盖的点设为
    不存在((disCo_x = -inf))。
    3. 当前点是需要选择的节点,即 (disCo_x = val),此时 (cnt = cnt + 1)
    (val) 为二分出的最小距离最大值的最小值,(cnt) 为选择的节点总数。

    然后跑个 (DFS),这题就切掉了。

    代码:

    # include <iostream>
    # include <cstdio>
    # define MAXN 300005
    # define INF 1145141919.810
    
    struct edge{
    	int v, next;
    }e[MAXN<<1];
    int hd[MAXN], cntE;
    bool explo[MAXN]; // is there a bomb or not
    int disCo[MAXN], disSel[MAXN];
    // dis to futhest uncovered key node, dis to closet selected key node
    int cntSel, n, m;
    
    void AddE(int u, int v);
    void DFS(int now, int fa, int val);
    bool Chk(int val);
    
    int main(){
    	scanf("%d%d", &n, &m);
    
    	for(int i = 1, x; i <= n; i++){
    		scanf("%d", &x);
    		explo[i] = x;
    	}
    
    	for(int i = 1, u, v; i <= n-1; i++){
    		scanf("%d%d", &u, &v);
    		AddE(u, v); AddE(v, u);
    	}
    
    	int l = 0, r = n;
    
    	while(l < r){
    		int mid = (l + r) >> 1;
    		if(Chk(mid)){
    			r = mid;
    		}
    		else{
    			l = mid+1;
    		}
    	}
    
    	printf("%d", l);
    
    	return 0;
    }
    
    bool Chk(int val){
    	cntSel = 0;
    	DFS(1, 0, val);
    
    	if(disCo[1] >= 0){
    		cntSel++;
    	}
    	return cntSel <= m; // 选择的节点不能超过 m 个
    }
    
    void DFS(int now, int fa, int val){ // val 为二分的最大值
    	disCo[now] = -INF, disSel[now] = INF;
    	
    	for(int i = hd[now]; i; i = e[i].next){
    		if(e[i].v == fa){
    			continue;
    		}
    		DFS(e[i].v, now, val);
    		disCo[now] = std::max(disCo[now], disCo[e[i].v] + 1);
    		disSel[now] = std::min(disSel[now], disSel[e[i].v] + 1);
    	}
    
    	if(explo[now] && disSel[now] > val){ // 自身是需要被覆盖的节点
    		disCo[now] = std::max(disCo[now], 0);
    	}
    	if(disCo[now] + disSel[now] <= val){ // 内部未被覆盖的点均被子树外的点覆盖
    		disCo[now] = -INF;
    	}
    	if(disCo[now] == val){ // 当前节点恰好是需要选择的节点
    		disCo[now] = -INF;
    		disSel[now] = 0;
    		cntSel++;
    	}
    }
    
    void AddE(int u, int v){
    	e[++cntE] = (edge){v, hd[u]};
    	hd[u] = cntE;
    }
    
  • 相关阅读:
    如何用纯 CSS 创作一个跳动的字母 i
    如何用纯 CSS 创作一个变色旋转动画
    如何用纯 CSS 创作气泡填色的按钮特效
    如何用纯 CSS 创作一个跳 8 字型舞的 loader
    如何用纯 CSS 创作一只徘徊的果冻怪兽
    如何用纯 CSS 创作一个单元素抛盒子的 loader
    如何用纯 CSS 创作单元素点阵 loader
    如何用纯 CSS 创作一个摇摇晃晃的 loader
    [Monkey King]
    473. 核电站问题
  • 原文地址:https://www.cnblogs.com/Foggy-Forest/p/13556368.html
Copyright © 2020-2023  润新知