NOIP2012 疫情控制 贪心+二分+倍增

7 篇文章 0 订阅
3 篇文章 0 订阅

一道很全(du)面(liu)的题
题目大意:给定一棵树,用最少的时间封住这棵树。
题解:
首先可以很容易发现一个条件:军队在走的时候都要尽量往上走,但不到根节点。因为越靠近根节点的点,控制的叶子节点越多。不过暴力是肯定会超时的,用倍增优化。
题目求的是最长移动时间军队的最短时间,想到二分答案。
但是还有这样一种情况:一支军队到达首都之后,又到达了另一个城市驻扎。
对于这种情况,我们需要记录所有到了首都之后还有剩余距离的军队,记录编号和剩余距离。
不能到达首都的军队,就封死自己最高能到的点就行了
然后再DFS一次,记录还没有被封死的,根节点的子树,记录距离。按距离给这些子树从大到小排序。
对于这些子树,如果有经过它到首都的军队,就取其中剩余距离最短的;如果没有,就取剩余路程最长的。

重(keng)点:

  • 排序后的编号与原编号不同,要记录一个原编号
  • 若一个军队只能在叶子节点,则该叶子节点也算被封死(主要是我写的时候顺序反了)
  • 若到达首都时剩余距离为0,则算作不能到首都
  • Check()大毒瘤,其他都是板子(233)
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long LL;
const int MAXN = 50001;

int n, m, Army[MAXN], Now_Army[MAXN];
int fir[MAXN], nxt[MAXN << 1], to[MAXN << 1], len[MAXN << 1], cnt;
int f[MAXN][18], dep[MAXN], du[MAXN], flag[MAXN], leaf[MAXN];
LL dis[MAXN], Min[MAXN];
int Color[MAXN], Need[MAXN], tag[MAXN];

struct Node{
    LL dis; int num;
}q[MAXN];

inline int read(){
    int k = 0, f = 1; char ch = getchar();
    while(ch < '0' || ch > '9'){if(ch == '-') f = -1; ch = getchar();}
    while(ch >= '0' && ch <= '9'){k = k*10 + ch - '0'; ch = getchar();}
    return k*f;
}

inline void add_edge(int a, int b, int l){
    len[cnt] = l, to[cnt] = b;
    nxt[cnt] = fir[a], fir[a] = cnt++;
}

inline bool cmp1(Node a, Node b){
    return a.dis > b.dis;
}

inline bool cmp2(int a, int b){
    return dis[a] > dis[b];
}

void dfs(int u, int fa){
    f[u][0] = fa;
    for(int i = 1; (1 << i) <= dep[u]; i++){
        f[u][i] = f[f[u][i - 1]][i - 1];
    }
    for(int i = fir[u]; i != -1; i = nxt[i]){
        int v = to[i];
        if(v == fa) continue;
        dep[v] = dep[u] + 1;
        dis[v] = dis[u] + len[i];
        du[u]++;
        dfs(v, u);
    }
    if(du[u] == 0) leaf[u] = true;
}

int Jump(int Cur, LL d){
    for(int j = 17; j >= 0; j--){
        if(f[Cur][j] > 1 && d - (dis[Cur] - dis[f[Cur][j]]) >= 0){
            d -= (dis[Cur] - dis[f[Cur][j]]);
            Cur = f[Cur][j];
        }
    }
    return Cur;
}

bool dfs2(int u, int fa){
    if(flag[u]) return false; //注意!!!这两个标记的优先度不同!!!
    if(leaf[u]) return true;
    for(int i = fir[u]; i != -1; i = nxt[i]){
        int v = to[i];
        if(v == fa) continue;
        if(dfs2(v, u)) return true;
    }
    return false;
}

bool Check(LL tim){
    memset(flag, false, sizeof(flag)); q[0].dis = Need[0] = 0;
    memset(Color, 0, sizeof(Color));
    for(int i = 1; i <= n; i++) flag[i] = Color[i] = 0;
    for(int i = 1; i <= m; i++) tag[i] = 0;
    for(int i = 1; i <= m; i++){
        int Top = Jump(Army[i], tim); //向上跳,但不可到根节点 
        if(dis[Army[i]] >= tim){
            flag[Top] = true;
        }
        else{
            q[++q[0].dis].dis = tim - dis[Army[i]]; //来首都后还能走多远
            q[q[0].dis].num = i;//来首都的时候就经过的城市
            if(!Color[Top] || q[q[0].dis].dis < Min[Top]){
                Min[Top] = q[q[0].dis].dis, Color[Top] = i;
            }
        }
    }
    if(!dfs2(1, 0)) return true;
    for(int i = fir[1]; i != -1; i = nxt[i]){
        int v = to[i];
        if(dfs2(v, 1)){
            Need[++Need[0]] = v; //需要支援的城市
        }
    }
    sort(q + 1, q + q[0].dis + 1, cmp1);
    sort(Need + 1, Need + Need[0] + 1, cmp2);
    int Cur = 1;
    for(int i = 1; i <= Need[0]; i++){
        if(!tag[Color[Need[i]]]){ //如果有来过这个城市的军队
            tag[Color[Need[i]]] = true; //选这个最小的
            continue;
        }
        while(tag[q[Cur].num] && Cur <= q[0].dis) Cur++; //已经使用
        if(Cur > q[0].dis) return false; //没军队了
        //从首都调军队
        if(q[Cur].dis < dis[Need[i]]) return false;
        tag[q[Cur].num] = true;
    }
    return true;
}

int main(){
    memset(fir, -1, sizeof(fir));
    n = read();
    for(int i = 1; i < n; i++){
        int a = read(), b = read(), l = read();
        add_edge(a, b, l), add_edge(b, a, l);
    }
    dep[1] = dis[1] = 0;
    dfs(1, 0);
    m = read();
    for(int i = 1; i <= m; i++){
        Army[i] = read();
    }
    int Temp = 0;
    for(int i = fir[1]; i != -1; i = nxt[i]) Temp++;
    if(m < Temp){printf("-1"); return 0;}

    long long l = 0, r = 1e14, mid;

    while(l < r){
        mid = (l + r) >> 1;
        if(Check(mid)) r = mid;
        else l = mid + 1;
    }
    printf("%lld", l);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
《信息学奥赛一本通 NOIP500 第1部分》是一本参考书籍,其主要目的是为了帮助准备参加NOIP(全国青少年信息学奥林匹克的省级选拔赛)的学生进行备考。这本书的内容丰富多样,涵盖了计算机科学和编程的各个方面。 在这本书的第1部分,主要介绍了NOIP500比赛的背景和要求。它首先详细解释了NOIP500的含义和意义,以及为什么要参加这样的比赛。它还介绍了NOIP500的考试形式和内容,包括必考的算法、数据结构和编程语言等。此外,它还介绍了评分标准和考试日期等重要信息。 第1部分还包括了一些备考的重要指导和技巧。它解释了如何正确准备和安排备考时间,以及如何理解和解答常见的考试题型。此外,书中还提供了一些实用的练习题和例子,帮助学生加深对知识点的理解和掌握。 这本书的语言简洁明了,结构清晰,非常适合初学者使用。它通过大量的例子和习题,帮助学生从基础知识开始逐步提高。此外,书中还附有一些重要的参考资料和学习资源,让学生能够更加全面地了解和学习计算机科学和编程。 总体而言,《信息学奥赛一本通 NOIP500 第1部分》是一本非常有价值的参考书籍,它能够为准备参加NOIP500的学生提供全面和系统的知识,帮助他们在竞赛中取得良好的成绩。对于对信息学感兴趣的学生,这本书也是一本很好的自学教材。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值