• 题解报告——魔法森林


    传送门

    题目描述

    为了得到书法大家的真传,小 E 同学下定决心去拜访住在魔法森林中的隐 士。魔法森林可以被看成一个包含 n 个节点 m 条边的无向图,节点标号为 1,2,3,…,n,边标号为 1,2,3,…,m。初始时小 E 同学在 1 号节点,隐士则住在 n 号节点。小 E 需要通过这一片魔法森林,才能够拜访到隐士。

    魔法森林中居住了一些妖怪。每当有人经过一条边的时候,这条边上的妖怪 就会对其发起攻击。幸运的是,在 1 号节点住着两种守护精灵:A 型守护精灵与 B 型守护精灵。小 E 可以借助它们的力量,达到自己的目的。

    只要小 E 带上足够多的守护精灵,妖怪们就不会发起攻击了。具体来说,无 向图中的每一条边 ei 包含两个权值 ai 与 bi 。若身上携带的 A 型守护精灵个数不 少于 ai ,且 B 型守护精灵个数不少于 bi ,这条边上的妖怪就不会对通过这条边 的人发起攻击。当且仅当通过这片魔法森林的过程中没有任意一条边的妖怪向 小 E 发起攻击,他才能成功找到隐士。

    由于携带守护精灵是一件非常麻烦的事,小 E 想要知道,要能够成功拜访到 隐士,最少需要携带守护精灵的总个数。守护精灵的总个数为 A 型守护精灵的 个数与 B 型守护精灵的个数之和。

    输入输出格式

    输入格式:

    输入文件的第 1 行包含两个整数 n,m,表示无向图共有 n 个节点,m 条边。 接下来 m 行,第i+ 1 行包含 4 个正整数 Xi,Yi,ai,bi,描述第i条无向边。 其中Xi与 Yi为该边两个端点的标号,ai 与 bi 的含义如题所述。 注意数据中可能包含重边与自环。

    输出格式:

    输出一行一个整数:如果小 E 可以成功拜访到隐士,输出小 E 最少需要携 带的守护精灵的总个数;如果无论如何小 E 都无法拜访到隐士,输出“-1”(不 含引号)。

    输入输出样例

    输入样例#1: 复制
    4 5 
    1 2 19 1 
    2 3 8 12 
    2 4 12 15 
    1 3 17 8 
    3 4 1 17 
    输出样例#1: 复制
    32
    
    输入样例#2: 复制
    3 1 
    1 2 1 1 
    输出样例#2: 复制
    -1

    说明

    * 解释1

    如果小 E 走路径 1→2→4,需要携带 19+15=34 个守护精灵; 如果小 E 走路径 1→3→4,需要携带 17+17=34 个守护精灵; 如果小 E 走路径 1→2→3→4,需要携带 19+17=36 个守护精灵; 如果小 E 走路径 1→3→2→4,需要携带 17+15=32 个守护精灵。 综上所述,小 E 最少需要携带 32 个守护精灵。

    * 解释2

    小 E 无法从 1 号节点到达 3 号节点,故输出-1。


    【思路分析】

    虽然这道题的归类是LCT,反正我是第一眼没有看出来的(怎么可以这么菜),所以我就去学习了一个神奇的“动态加边spfa”。

    我们首先按a的大小进行排序,我们每次以b的大小跑spfa。我们依次加入按a排好序的边,然后进行连边,其实我们这样操作就是枚举路径a的max,很明显我们之前连的边的a一定都小于当前这条边的a,然而如果有边的a比当前边的a大的话不用管,因为我们还没有连边呢,这样就保证了我们当前跑的图的所有边的a不会大于max。

    具体操作如下:

    1 for(int i=1;i<=m;i++)
    2     {
    3         add_edge(line[i].from,line[i].to,line[i].a,line[i].b);
    4         add_edge(line[i].to,line[i].from,line[i].a,line[i].b);
    5         spfa(line[i].from,line[i].to);
    6         ans=min(ans,dis[n]+line[i].a);
    7     }

    这里有个值得注意的地方,我开始码的时候,每次都只将起点入队然后跑spfa(有没有跟我同样想法的!!!),然后交上去只有10分(后来调试出来后才发现,能给10分已是天大荣幸了)。经过一番斗争(加上参考大佬代码)发现我们每次入队的应该是当前边的两点。

    1 spfa(1);

    1 spfa(line[i].from,line[i].to);

    证明这样做的正确性:

    我一开始疑惑的是如果这样入队,那就不一定保证从起点出发了啊,然后细细一想才发现自己有多蠢,我们如果可以更新当前这条边的前提是这条边已经和起点联通了,不然就没法更新,所以一定可以从起点出发。

    证明从起点更新的错误性:

    如果我们每次将起点入队发现我们能从起点更新的边之前已经更新过了,因此我们不能再将其入队了(因为spfa是有更优解才继续),然后就发现我们找不到最优解了。当然有人会说那我们将判定更新条件的<改为<=,然后就发现死循环了。


    【代码实现】

     1 #include<cstdio>
     2 #include<queue>
     3 #include<cctype>
     4 #include<algorithm> 
     5 using namespace std;
     6 void read(int &x)
     7 {
     8     char ch;int f;
     9     while(!isdigit(ch=getchar())&&ch!='-'); ch=='-'?(x=0,f=-1):(x=ch-'0',f=1);
    10     while(isdigit(ch=getchar())) x=x*10+ch-'0';
    11 }
    12 const int N=5e4+5;
    13 const int M=1e5+5;
    14 const int INF=1e9+7;
    15 struct sd{
    16     int from,to,a,b;
    17 }line[M];
    18 struct sd1{
    19     int to,next,a,b;
    20 }edge[M<<1];
    21 int head[N],dis[N],cnt,n,m,a1,b1,ans=INF;
    22 bool vis[N];
    23 queue<int> que;
    24 bool cmp(sd x,sd y){return x.a<y.a;}
    25 void add_edge(int from,int to,int a,int b)
    26 {
    27     edge[++cnt].next=head[from];
    28     edge[cnt].to=to;
    29     edge[cnt].a=a,edge[cnt].b=b;
    30     head[from]=cnt;
    31 }
    32 void spfa(int from,int to)
    33 {
    34     que.push(from),que.push(to),vis[from]=1,vis[to]=1;
    35     while(!que.empty())
    36     {
    37         int v=que.front();que.pop(),vis[v]=0;
    38         for(int i=head[v];i;i=edge[i].next)
    39         {
    40             int to=edge[i].to;
    41             if(max(edge[i].b,dis[v])<dis[to])
    42             {
    43                 dis[to]=max(edge[i].b,dis[v]);
    44                 if(!vis[to])
    45                 que.push(to),vis[to]=1;
    46             }
    47         }
    48     }
    49 }
    50 int main()
    51 {
    52     read(n),read(m);
    53     for(int i=1;i<=m;i++)
    54     read(line[i].from),read(line[i].to),read(line[i].a),read(line[i].b);
    55     sort(line+1,line+m+1,cmp);
    56     fill(dis+1,dis+1+n,INF);
    57     dis[1]=0;
    58     for(int i=1;i<=m;i++)
    59     {
    60         add_edge(line[i].from,line[i].to,line[i].a,line[i].b);
    61         add_edge(line[i].to,line[i].from,line[i].a,line[i].b);
    62         spfa(line[i].from,line[i].to);
    63         ans=min(ans,dis[n]+line[i].a);
    64     }
    65     if(ans==INF) printf("-1");
    66     else printf("%d",ans);
    67     return 0;
    68 }
  • 相关阅读:
    latex在vim中的代码片段
    latex设置不同中英文字体
    React 路由基本配置
    React网络请求
    React生命周期函数
    React父子传值中propTypes defaultProps
    React父子组件传值
    React todolist案例和持久化实现
    React表单
    react事件对象 、键盘事件、 表单事件 、ref获取dom节点、React实现类似vue双向数据绑定
  • 原文地址:https://www.cnblogs.com/genius777/p/9195616.html
Copyright © 2020-2023  润新知