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

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

重(keng)点:

  • 排序后的编号与原编号不同,要记录一个原编号
  • 若一个军队只能在叶子节点,则该叶子节点也算被封死(主要是我写的时候顺序反了)
  • 若到达首都时剩余距离为0,则算作不能到首都
  • Check()大毒瘤,其他都是板子(233)
#include
#include
#include
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;
}

你可能感兴趣的:(图论,贪心,二分,倍增)