【BZOJ3669】魔法森林(SPFA动态加边)

题目连接

题目描述

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

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

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

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

题意简述: 给你一张无向带权图,每条边有两种权值。求出一条从 1到n 的路径使得路径上的两种权值的最大值之和最小,并输出这个最小值

题解

首先此题不可二分,因为二分跨度大,可能导致漏掉最优解。
既然二分不行我们就枚举?
新套路 SPFA 动态加边。(看起来是个暴力,但每次重新跑SPFA时实际复杂度应该不高)
先把边按 a的值 从小到大排序,让后枚举最大的a值,再一条条把边加进来,以b为权值跑SPFA。
注意我们保证了a单调递增,那么每次加边后重新SPFA时 b 的值一定是递减的,因此不用重新设dis为INF。
另外加边的时候是把边的端点加到队列里,新加的边要起作用肯定是要经过它,那么只需加相连的点(也只有加相连的点才有用)。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<queue>
using namespace std;
inline int read()
{
    int x=0;char ch=getchar();int t=1;
    for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') t=-1;
    for(;ch>='0'&&ch<='9';ch=getchar()) x=(x<<1)+(x<<3)+(ch-48);
    return x*t;
}
const int M=1e5+10;
const int N=5e4+10;
const int INF=1e9;
struct edge{
    int from,to,next;int a,b;
}ed[M],e[M<<1];
int head[N];
int dis[N];
int id[M];
bool g[N];
int n,m;
queue<int> Q;
inline bool cmp(int x,int y)
{
    return ed[x].a<ed[y].a;
}
inline void add(int x,int y,int a,int b,int& cnt,edge* ed,int op)
{
    ed[++cnt]=(edge){x,y,head[x],a,b};
    if(op==1) head[x]=cnt;
}
inline void SPFA()
{
    while(!Q.empty()){
        register int p=Q.front();Q.pop();
        for(register int v,i=head[p];i;i=e[i].next)
        {
            v=e[i].to;
            if(dis[v]<=max(dis[p],e[i].b)) continue;
            dis[v]=max(dis[p],e[i].b);
            if(!g[v]) Q.push(v),g[v]=1;
        }
        g[p]=0;
    }
}
int main()
{
    n=read();m=read();register int x,y,a,b;dis[1]=0;
    int cnt1=0,cnt2=0;
    for(register int i=2;i<=n;i++) dis[i]=INF;
    for(register int i=1;i<=m;i++) {x=read();y=read();a=read();b=read();add(x,y,a,b,cnt1,ed,0);id[i]=i;}
    sort(id+1,id+1+m,cmp);
    int h=1;
    int ans=INF;int T=0;
    while(h<=m){
        int sta=ed[id[h]].a;
        for(;h<=m;h++){
            if(ed[id[h]].a!=sta) break;
            edge ne=ed[id[h]];
            add(ne.from,ne.to,ne.a,ne.b,cnt2,e,1);
            add(ne.to,ne.from,ne.a,ne.b,cnt2,e,1);
            if(!g[ne.from]) Q.push(ne.from),g[ne.from]=1;
            if(!g[ne.to]) Q.push(ne.to),g[ne.to]=1;
        }
        SPFA();
        ans=min(ans,sta+dis[n]);
    }
    if(dis[n]==INF) printf("-1\n");
    else printf("%d\n",ans);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: bzoj作为一个计算机竞赛的在线评测系统,不仅可以提供大量的题目供程序员练习和学习,还可以帮助程序员提升算法和编程能力。为了更好地利用bzoj进行题目的学习和刷题,制定一个bzoj做题计划是非常有必要的。 首先,我们需要合理安排时间,每天留出一定的时间来做bzoj的题目。可以根据自己的时间安排,每天挑选适量的题目进行解答。可以先从难度较低的题目开始,逐渐提高难度,这样既能巩固基础知识,又能挑战自己的思维能力。 其次,要有一个计划和目标。可以规划一个每周或每月的题目数量目标,以及每个阶段要学习和掌握的算法知识点。可以根据bzoj的题目分类,如动态规划、图论、贪心算法等,结合自己的实际情况,有针对性地选择题目进行学习。 此外,要充分利用bzoj提供的资源。bzoj网站上有很多高质量的题解和优秀的解题代码,可以参考和学习。还有相关的讨论区,可以与其他程序员交流和讨论,共同进步。 最后,要坚持并保持思考。做题不是单纯为了刷数量,更重要的是学会思考和总结。遇到难题时,要有耐心,多思考,多尝试不同的解法。即使不能一次性解出来,也要学会思考和分析解题过程,以及可能出现的错误和优化。 总之,bzoj做题计划的关键在于合理安排时间、制定目标、利用资源、坚持思考。通过有计划的刷题,可以提高算法和编程能力,并培养解决问题的思维习惯,在计算机竞赛中取得更好的成绩。 ### 回答2: bzoj做题计划是指在bzoj这个在线测评系统上制定一套学习和刷题的计划,并且将计划记录在excel表格中。该计划主要包括以下几个方面的内容。 首先是学习目标的设定。通过分析自己的水平和知识缺口,可以设定一个合理的目标,比如每天解决一定数量的题目或者提高特定的算法掌握程度。 其次是题目选择的策略。在excel表格中可以记录下自己选择的题目编号、题目类型和难度等信息。可以根据题目的类型和难度来安排每天的刷题计划,确保自己可以逐步提高技巧和解题能力。 然后是学习进度的记录和管理。将每天的完成情况记录在excel表格中,可以清晰地看到自己的学习进度和任务完成情况。可以使用图表等功能来对学习进度进行可视化展示,更好地管理自己的学习计划。 同时,可以在excel表格的备注栏中记录下每道题目的解题思路、关键点和需要复习的知识点等信息。这样可以方便自己回顾和总结,巩固所学的知识。 最后,可以将excel表格与其他相关资料进行整合,比如算法教材、题目解析和学习笔记等。这样可以形成一个完整的学习档案,方便自己进行系统的学习和复习。 总之,bzoj做题计划excel的制定和记录可以帮助我们更加有条理和高效地进行学习和刷题。通过合理安排学习目标和题目选择策略,记录学习进度和思路,并整合其他学习资料,我们可以提高自己的解题能力,并在bzoj上取得更好的成绩。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值