【bfs+链式向前星】防御僵尸(defend)计蒜客

题目:

A 国有 n 座城市,n1 条双向道路将这些城市连接了起来,任何两个城市都可以通过道路互通。

某日,A 国爆发了丧尸危机,所有的幸存者现在都聚集到了 A 国的首都(首都是编号为 1 的城市)。而其它城市的丧尸会时不时地向首都发起进攻。

为了抵御丧尸的攻击,幸存者在 A 国的每座城市都修建了防御工事。编号为 i 的城市,其防御工事强度为 di。

当一只丧尸决定进攻首都时,它会从某城市 u(u1) 沿经过道路数量最少的路径前往首都。沿途,这只丧尸会试图突破当地的防御工事。

对于某座防御工事,如果丧尸的凶猛值不小于该防御工事的强度,丧尸就会突破这座防御工事,否则丧尸会被阻截在此。

注意:如果丧尸突破了某城市的防御工事,并代表此防御工事被破坏。该防御工事在面对后面来的丧尸的时候还会保持原先的强度。

输入格式

第一行,包含一个正整数 n。

第二行包含 n 个正整数,第 i 个正整数表示编号为 i 的城市的防御工事的强度 d_i

接下来 n-1 行,每行有两个空格隔开的整数 u,v,表示编号为 u 的城市与编号为 v 的城市之间有一条双向道路。

接下一行,包含一个正整数 q,表示发生了 q 次丧尸进攻首都的事件。

接下来 q 行,每行两个整数 u,x,表示有一只凶猛值为 x 的丧尸从编号为 u 的城市出发,进攻首都。

输出格式

输出 q 行,按照输入给出的顺序输出每个丧尸分别被阻截在了哪座城市。如果丧尸突破了所有的防御工事,攻入了首都,则请输出

The zombie ate your brains!

数据范围

本题包含 10 个测试点

对于前三个测试点,保证 n100

对于第四个到第六个测试点,保证第 i 条边连接了城市 i 和城市 i+1

对于全部测试点,保证 1n2×10^5,1di,x100,u1,1q2×10^5

输出时每行末尾的多余空格,不影响答案正确性

要求使用「文件输入输出」的方式解题,输入文件为 defend.in,输出文件为 defend.out

Sample Input

5
8 2 7 1 4
1 2
1 3
3 4
3 5
5
2 1
2 9
3 6
4 5
5 7

Sample Output

2
The zombie ate your brains!
3
3
1

题解:

很容易发现该题是最短路径问题,而且的记录路径的最短路径问题。

根据 题目我们有两种思路,第一个是对每个询问单独处理求点到点的最短路在寻路过程中计算结果,第二种是先对图进行处理求出点到所有点的最短路。

第一种方法,求两点之间最短距离的比较稳定的算法是Dijkstra算法,复杂度是O(m*logn).而后有q个查询复杂度应该是O(q*m*logn),q,m,n最大值都为2e5,明显会超时,由于这题图的特殊性,它是一个无权图,就是说边的权值默认是1,其实还可以用bfs和dfs求两点最短路,但时间复杂度也不理想因为拥有q个询问,就算复杂度为线性还是会超时。

第二种方法是,先预处理,求点到其他点的最短路径算法还是是Dijkstra算法,这个算法是以bfs为基础改进而来的,同样因为这题图的特殊性,用bfs就可以计算出点到其他点的最短路,然后最大的问题是如何储存路径,如果之间按点来储存所有路径,那在最坏情况(图为链状,比如 图是 1-2-3-4,对点储存就是:2:2-1,3:3-2-1,4:4-3-2-1。)空间复杂度会达到n^2级别,很有可能超内存,同时数据复制带来的的时间复杂度也会提高。从刚刚的链状图的路径储存,我们会发现有很多重复的部分,就利用他来压缩路径。

构造一个数组path[i]=j,其中i指第i个点的最短路的最后一个路径是i-j。用这种方法就可以将最短路路径完美的储存在数组里面。

                               |0,1,2,3,4,5,6|

用它获得路径的方法也很简单,比如我们有这样子的数组path[]={0,0,1,2,2,6,1},我们要获得从5到1的路径,path[5]=6,path[6]=1,说明5到1的路径是5-6-1,同样,我们要获得3到1的路径,path[3]=2,path[2]=1,所以3到1的路径是,3-2-1。

加入一个新的点也很简单,只要找到这个点的最短路的最后一步直接加在数组后面就可以,这也正好符合了bfs搜索的方法。

因为这题卡时间复杂度比较厉害,所以再提供3个优化思路。

一 链式向前星存边:

储存图不要使用常用的方便的vector<int>e[n],因为vector的分配内存和改变迭代器很消耗时间。所以使用链式向前星来储存,会很大程度的优化时间复杂度,一定程度的优化空间复杂度。

链式向前星和前文中储存路径的方法有些许类似,实际上链式向前星是一种数组模拟单向链表的操作。

一般的链式向前星的主体是3个数组加一个整数,head[],to[],next[],k(一般会写成结构体,如果有权值可在加入一个数组储存权值),

head[i]=j,指点i的边的最后一个索引是j  to[i]=j,指索引i指向的点是j  next[i]=j,指索引i的下一个索引是j,k指边的个数.

使用前先对索引初始化,将所有索引置为-1,即将head,next置为-1;

加入新边的操作是给予新边一个索引(就是k),将新边的目标点存入相应的to数组里面,将相应的next更新为head的值,然后将head的值更新为这个新边的索引。

遍历结构体的方法与之前的路径遍历方法类似。

包装的结构体

struct Edge {
    int to, next;
};
struct  CStar {

    Edge edge[400005];
    int head[400005];
    int k;
    CStar() {
        k = 0;
        for (int i = 0; i < 400005; i++) {
            head[i] = -1;
            edge[i].next = -1;
        }
    }
    void push(int a, int b) {
        edge[k].to = b;
        edge[k].next = head[a];
        head[a] = k++;
    }
};

遍历边的操作

for (int i = e.head[x]; ~i; i = e.edge[i].next) 
...
            

二 路径最大值:

在每一个查询中遍历路径进行计算也有挺高的复杂度,特别是在僵尸凶猛值很大时,这样它就要遍历一整条路径。我们可以用一些方法来避免出现遍历整条路径的情况,我们可以在计算最短路径时顺势算出最短路径中的最大防御值,并储存在数组中。这样我们最后进行查询时如果僵尸凶猛值大于等于这个路径防御最大值就可以直接输出“The zombie ate your brains!”

三 路径优化:

这个方法理论上可以一定程度减少复杂度,但我个人在本题样例上使用的并不那么有效,但也提出了作为一个思路扩展。

这个思路是基于当你大于等于一个值x后就肯定大于等于一个小于等于x的值。

所以我们可以以此思路对路径进行优化,使路径的每一条路变得严格递增,从而优化查询时间。

例子:

可以变化成

可以在不改变结果的情况下降低树的深度,从而降低查询时间。

用文字描述过程就是,将一个点连到它最短路上第一个大于等于它值的数上,直到它达到首都(也就是编号为1的点)

AC代码:

#include<iostream>
#include<queue>
#include<algorithm>
#include<cctype>

using namespace std;

struct Edge {
    int to, next;
};

struct  CStar {

    Edge edge[400005];
    int head[400005];
    int k;
    CStar() {
        k = 0;
        for (int i = 0; i < 400005; i++) {
            head[i] = -1;
            edge[i].next = -1;
        }
    }
    void push(int a, int b) {
        edge[k].to = b;
        edge[k].next = head[a];
        head[a] = k++;
    }
};

CStar e;

int d[200005];
int path[200005];
int book[200005];
int ld[200005];
queue<int>qu;

int main() {
#if 1
    freopen("defend.in", "r", stdin);
    freopen("defend.out", "w", stdout);
#endif

    int n, q, a, b;
    int x, u, flag, len, it;
    scanf("%d", &n);

    for (int i = 1; i <= n; ++i) {
        scanf("%d", d + i);
    }

    for (int i = 1; i < n; ++i) {
        scanf("%d%d", &a, &b);
        e.push(a, b);
        e.push(b, a);
    }

    qu.push(1);
    book[1] = 1;
    path[1] = 0;
    ld[1] = d[1];
    while (!qu.empty()) {
        x = qu.front();

        for (int i = e.head[x]; ~i; i = e.edge[i].next) {
            it = e.edge[i].to;
            if (book[it] == 0) {
                book[it] = 1;
                path[it] = x;
                ld[it] = max(ld[x], d[it]);
                qu.push(it);
            }
        }
        qu.pop();

    }

    scanf("%d", &q);
    while (q--) {
        flag = 1;
        scanf("%d%d", &u, &x);
        if (ld[u] <= x)printf("The zombie ate your brains!
");
        else {
            while (u) {
                if (x < d[u]) {
                    flag = 0;
                    printf("%d
", u);
                    break;
                }
                u = path[u];
            }
            if (flag)printf("The zombie ate your brains!
");
        }
    }

    return 0;
}
原文地址:https://www.cnblogs.com/komet/p/12956337.html