zoukankan      html  css  js  c++  java
  • cogs 826. [Tyvj Feb11] GF打dota 次短路详细原创讲解! dijkstra

     

    826. [Tyvj Feb11] GF打dota

    ★★☆   输入文件:dota.in   输出文件:dota.out   简单对比
    时间限制:1 s   内存限制:128 MB

    众所周知,GF同学喜欢打dota,而且打得非常好。今天GF和Spartan同学进行了一场大战。

    现在GF拿到一张地图,地图上一共有n个地点,GF的英雄处于1号点,Spartan的基地位于n号点,

    GF要尽快地选择较短的路线让他的英雄去虐掉Spartan的基地。但是Spartan早就料到了这一点,

    他有可能会开挂(BS~)使用一种特别的魔法,一旦GF所走的路线的总长度等于最短路的总长度时,

    GF的英雄就要和这种魔法纠缠不休。这时GF就不得不选择非最短的路线。现在请你替GF进行规划。



    对于描述的解释与提醒:
    1.无向路径,花费时间当然为非负值。

    2.对于本题中非最短路线的定义:不管采取任何迂回、改道方式,

    只要GF所走的路线总长度不等于1到n最短路的总长度时,就算做一条非最短的路线。

    3.保证1~n有路可走。

    输入:

    第一行为n,m(表示一共有m条路径)
    接下来m行,每行3个整数a,b,c,表示编号为a,b的点之间连着一条花费时间为c的无向路径。
    接下来一行有一个整数p,p=0表示Spartan没有开挂使用这种魔法,p=1则表示使用了。

    输出:

    所花费的最短时间t,数据保证一定可以到达n。

    样例输入1:
    5 5
    1 2 1
    1 3 2
    3 5 2
    2 4 3
    4 5 1
    0


    样例输入2:
    5 5
    1 2 1
    1 3 2
    3 5 2
    2 4 3
    4 5 1
    1

     
    样例输出1:
    4
    样例输出2:
    5

    对于50%的数据,1<=n,m<=5000
    对于70%的数据,1<=n<=10000, 1<=m<=50000,p=0,
    对于100%的数据,1<=n<=10000, 1<=m<=50000,p=1
    无向图,花费时间c>=0

    各个测试点1s

     
    来源:lydliyudong    Tyvj February二月月赛第二场  第2道

    敬爱的读者:在这里抱歉地声明 接下来的一大段呕心沥血的讲解皆为不完全正确的

    我一度认为 用一个dis2数组来表示从根节点到每一个点的次短路的值

    但是经过认真思考 和跌坑儿后  我发现 不能这样子去做

    求出对于一个点的次短路 并不能向另外的一个点去扩展

    所以下面的内容仅做思维拓展(只有70分,我下面这个冒险的自创算法有不正确的情况存在!)

    这一道题真的是太难了额,发现自己最近总是冲动想要自己去研究一些没学过的东西

    那么怎样子用dijkstra来解决这一道坑人的题呢?

    首先我们先来回忆一下dijkstra最短路是怎样求的

    就是利用一个优先队列 往里面放一个个pair <dis[x],x> 去一个个松弛

    那么求次短路怎么办呢?

    突然间 我想到了一个异常暴力的做法

    就是说 最短路肯定是整张图中最优最优的路径了 随便改变一下路径上的那一条边都不是最短路了

    那么我们可以枚举一下删掉哪一条边 每一次跑一遍在删掉当前所枚举的边后的新图上跑dijkstra

    很显然这个想法是非常正确的 也是没有什么意义的 因为这肯定效率非常的低下啊

    所以我们要来想一种办法 使得在跑最短路的同时求出次短路?!?<<这真是一个神奇又看起来不太可能的想法

    原来不是有一个数组是dis数组 来记录每一个点到根节点最短路的长度

    那么我们再来设一个dis2数组 当然非常显然针对这一道题是没有什么很大的必要的 

    但是这样子可能会对我们以后解决一些比较共性的问题会有很大的帮助的♪(^∇^*)

    经过10分钟的沉思 我终于想出了一种解决办法

    您这么想:

    我们再进行松弛操作的时候是从小到大的   

    有以下这么几种情况

    1.现在枚举的dis[y]+v[x][i]<dis2[x]

    也就是说 找到了更短的次短路 就swap一下

    2.dis2[x]<dis1[x]

    也就是说次短路比最短路还要短

    那次短路和最短路 调一个个儿就行了

    那么怎么证明上面的方法是正确的呢?

    首先你第一个枚举的肯定是最小的对吧

    你会在第一次循环中先把dis2更新

    现在的dis2比dis1小 就swap一下  那么当前的最短路就是当前枚举的  次短路还是正无穷 

    其实这样子讲还是太抽象了

    首先看第一个情况 更新次短路 这肯定是没有什么问题了 有更短的就更新一下 很正确啊

    第二个情况呢?

    如果dis2<dis1 就swap    对于最短路肯定是没有问题的 这样子dis1就成了枚举到当前时 的最短路

    其实dis2的正确性也是可以保证的 因为dis1是原来先前所有情况中最短的   现在往后顺移 就成了第二短的 没有任何问题!

    所以我们就可以付诸实施这个本人自己想出来的绝妙办法 啦

    可惜只有70分 WA了30

    #include<bits/stdc++.h>
    #define maxn 10005
    #define maxm 50005
    #define pa pair<int,int>
    using namespace std;
    int n,m;
    vector<int> v[maxn],w[maxn];
    int dis1[maxn],dis2[maxn];
    int vis[maxn];
    priority_queue<pa,vector<pa>,greater<pa> > q;
    void Dijkstra()
    {
        memset(dis1,0x3f,sizeof(dis1));
        memset(dis2,0x3f,sizeof(dis2));
        dis1[1]=0;
        dis2[1]=0;
        q.push(make_pair(0,1));
        while(!q.empty())
        {
            int x=q.top().second;
            q.pop();
            if(vis[x])
                continue;
            vis[x]=1;
            for(int i=0;i<v[x].size();i++)
            {
                int y=v[x][i];
                if(dis2[y]>dis1[x]+w[x][i])
                    dis2[y]=dis1[x]+w[x][i];
                if(dis2[y]<dis1[y])
                    swap(dis2[y],dis1[y]);
                q.push(make_pair(dis1[y],y));
            }
        }
    }
    int main()
    {
        freopen("dota.in","r",stdin);
        freopen("dota.out","w",stdout);
        scanf("%d%d",&n,&m);
        for(int i=1;i<=m;i++)
        {
            int x,y,z;
            scanf("%d%d%d",&x,&y,&z);
            v[x].push_back(y);
            v[y].push_back(x);
            w[x].push_back(z);
            w[y].push_back(z);
        }
        Dijkstra();
        int p;
        scanf("%d",&p);
        if(p)
            printf("%d",dis2[n]);
        else
            printf("%d",dis1[n]);
        return 0;
    }
    这是70分的代码

     

    经过3天的苦苦思考 我仿佛想出了一个反例

    就是说如果按照我上面的做法 可能会出现 一种情况

    就是 求出来的次短路和最短路的数值是相同的

    比如有两条路径走出来都是4

    那么最短路和次短路答案就都成了4

    非常的苦闷 

     现在我们还是来正八经儿地讲一下比较普遍认可的做法吧(我太蒟了 自创了一个算法错了)

     好 ,这一道题的正解是

    跑两边dijkstra  是不是非常的荒唐?

    就是以1为根正着来一遍 以n为根反着来一遍 

    正着的是dis1数组 反着的是dis2数组 我们可以发现

    我们可以枚举每一条边

    这一条边的两个端点是u v

    u比较靠近1  v比较靠近n

    那么我们只要找到最小的不等于最短路的次短路即可

    就是dis1[u]+边权+dis2[v]中取一个最小的即可

    然而 还是70分  严重抗议!这一道题有问题吧~

    #include<bits/stdc++.h>
    #define maxn 10005
    #define pa pair<int,int>
    using namespace std;
    int vis1[maxn],vis2[maxn],vis3[maxn],dis1[maxn],dis2[maxn];
    priority_queue<pa,vector<pa>,greater<pa> > q;
    vector<int> v[maxn],w[maxn];
    int n,m; 
    void Dijkstra1()
    {
        memset(dis1,0x3f,sizeof(dis1));
        dis1[1]=0;
        q.push(make_pair(0,1));
        while(!q.empty())
        {
            int x=q.top().second;
            q.pop();
            if(vis1[x])
                continue;
            vis1[x]=1;
            for(int i=0;i<v[x].size();i++)
            {
                int y=v[x][i];
                if(dis1[y]>dis1[x]+w[x][i])
                {
                    dis1[y]=dis1[x]+w[x][i];
                    q.push(make_pair(dis1[y],y));
                }
            }
        }
    }
    void Dijkstra2()
    {
        memset(dis2,0x3f,sizeof(dis2));
        dis2[n]=0;
        q.push(make_pair(0,1));
        while(!q.empty())
        {
            int x=q.top().second;
            q.pop();
            if(vis2[x])
                continue;
            vis2[x]=1;
            for(int i=0;i<v[x].size();i++)
            {
                int y=v[x][i];
                if(dis2[y]>dis2[x]+w[x][i])
                {
                    dis2[y]=dis2[x]+w[x][i];
                    q.push(make_pair(dis2[y],y));
                }
            }
        }
    }
    int Min=0x3f3f3f3f;
    void Dfs(int x)
    {
        if(vis3[x])
            return;
        vis3[x]=1;
        for(int i=0;i<v[x].size();i++)
        {
            int y=v[x][i];
            int Dis=dis1[x]+w[x][i]+dis2[y];
            if(Dis!=dis1[n]&&Dis<Min)
                Min=Dis;
            Dfs(y);
        }
    }
    int main()
    {
        freopen("dota.in","r",stdin);
        freopen("dota.out","w",stdout);
        scanf("%d%d",&n,&m);
        for(int i=1;i<=m;i++)
        {
            int x,y,z;
            scanf("%d%d%d",&x,&y,&z);
            v[x].push_back(y);
            v[y].push_back(x);
            w[x].push_back(z);
            w[y].push_back(z);
        }
        Dijkstra1();
        int p;
        scanf("%d",&p);
        if(p==0)
        {
            printf("%d",dis1[n]);
            return 0;
        }
        Dijkstra2();
        Dfs(1);
        printf("%d",Min);
        return 0;
     }
    已然是70分 谁来救救我啊

    下面上一个神奇的满分做法 A*

    #include<bits/stdc++.h>
    #include<iostream>
    #include<iomanip>
    #include<cstdio>
    #include<cstdlib>
    #include<cstring>
    #include<string>
    #include<algorithm>//系统快排必加
    #include<cmath>
    #include<queue>
    #include<vector>
    using namespace std;
    const int maxn=10005;
    int n,m,dis[maxn];vector<int>son[maxn],v[maxn];
    bool bein[maxn];queue<int>q;
    void Set(int prt,int to,int d){
        son[prt].push_back(to),v[prt].push_back(d);
    }
    void spfa(int s){
        memset(dis,0x3f,sizeof(dis));
        dis[s]=0,q.push(s),bein[s]=true;
        while(!q.empty()){
            int rt=q.front();q.pop(),bein[rt]=false;
            for(int i=0;i<son[rt].size();i++){
              int to=son[rt][i];
              if(dis[to]>dis[rt]+v[rt][i]){
                  dis[to]=dis[rt]+v[rt][i];
                  if(!bein[to])bein[to]=true,q.push(to);
              }    
            }
        }
    }
    struct node{
        int x,d;
        bool operator<(const node &a)const{
          return d+dis[x]>a.d+dis[a.x];
        }
    };priority_queue<node>Q;
    int Astar(int s,int t){
        Q.push((node){s,0});
        while(!Q.empty()){
            node prt=Q.top();Q.pop();int rt=prt.x;
            if(rt==t&&prt.d>dis[s])return prt.d;
            for(int i=0;i<son[rt].size();i++)
              Q.push((node){son[rt][i],prt.d+v[rt][i]});
        }
    }
    int main()
    {
       freopen("dota.in","r",stdin);
       freopen("dota.out","w",stdout);
       int x,y,z,p;
       cin>>n>>m;
       int i;
       while(m--)
         cin>>x>>y>>z,Set(x,y,z),Set(y,x,z);
       spfa(n);
       cin>>p;
       if(p==0)
         cout<<dis[1];
       else
         cout<<Astar(1,n);
       return 0;
    }
  • 相关阅读:
    Java并发编程:同步容器
    Java并发编程:深入剖析ThreadLocal
    使用jQuery开发一个响应式超酷整合RSS信息阅读杂志
    Javascript 严格模式
    参数传递的四种形式----- URL,超链接,js,form表单
    《CSS 设计指南》学习笔记 一
    【BootStrap】初步教程
    JavaScript日期对象使用总结
    Web前端知识技能大汇总
    浏览器 CSS Hack 收集
  • 原文地址:https://www.cnblogs.com/Tidoblogs/p/11370573.html
Copyright © 2011-2022 走看看