Dijkstra+优先队列——Bridges and Tunnels

题意

不久后滑铁卢将会变得非常冷,但是幸运的是,很多建筑都被桥梁和隧道连接着,所以你不需要总是走在外面。但是现在建筑

物之间的连接是错综复杂的,很难知道某两个建筑物之间的最优路线,所以需要你写程序判断。

给出 n 个点,m 条无向边,以及 p 个查询,边分为两种,一种是暴露在外面的边,用 O 表示,另一种是在室内的边,用 I 表示;最优

路线遵循以下规则:

1)尽可能使得路径上暴露在外面的边权值和最少;

2)在满足第一个条件的情况下,尽可能使得总路程最少。

每次查询给出一个 起点s 和终点 t,求  s -> t 的最优路线。若路线存在则输出路径上暴露在外面的边的总和,以及路径的总和;否则输出“IMPOSSIBLE”。

 

分析//最好结合代码来看,我有写注解

dijkstra(以下简称DIJ)最短路变形。在最短路的基础上加费用的经典变形。“规则”是最短路进行的条件,分四种情况:

用outd[v] 代表最短路到 v 点时室外边的最小和,sumd[v] 代表到 v 点时路径总和

1)当前边属于室内边时

a) 若 outd[v] > outd[u],更新 outd[v] 以及 sumd[v],v入队;

b) 若 outd[v] == outd[u] &&sumd[v] > sumd[u] + dis[u, v],更新 outs[v] 以及 sumd[v],v 入队;

2)当前边属于室外边时

c) 若outd[v] > outd[u] + dis[u, v],更新 outd[v] 以及 sumd[v],v入队;

d) 若 outd[v] == outd[u] + dis[u, v]&& sumd[v] > sumd[u] + dis[u, v],更新 outd[v] 以及 sumd[v],v 入队。

 

但DIJ算法每一次都要找当前路径最小的点,从而对他进行下一步的处理,这一题如果单纯找路径最小的点会超时,所以要优化。

 

知识点

1.DIJ算法

自己看数据结构那本书,挺详细的,主要去看186页那里如何一步步实现,但有两个错误的地方,第一个是图5-25,应该是v2指向v3,而不是v3指向v2,第二个是代码,186页倒数第7行,for(k=0;k<N;k++)而不是j++。

2.priority_queue//这个真的很叼,很有用

优先队列是队列的一种,不过它可以按照自定义的一种方式(数据的优先级)来对队列中的数据进行动态的排序//这就是优化的办法

每次的push和pop操作,队列都会动态的调整,以达到我们预期的方式来存储。

priority_queue 对于基本类型的使用方法相对简单。他的模板声明带有三个参数:

priority_queue<Type, Container,Functional>

其中Type 为数据类型,Container 为保存数据的容器,Functional 为元素比较方式。

详细:

http://www.cnblogs.com/flyoung2008/articles/2136485.html(必看)

http://www.cnblogs.com/void/archive/2012/02/01/2335224.html

这个队列又涉及到几个知识点(但在此题我觉得只要照搬他的模板就好 即我没去深究)

1.重载运算符(由于类型有很多,请自己上网百度)。

2.初始化列表

http://www.cnblogs.com/graphics/archive/2010/07/04/1770900.html

 

 

修改前:

struct P
{
    int to;
    ll out;
    ll sum;
    P() {}
    P(int _to, ll _out, ll _sum) :to(_to), out(_out), sum(_sum) {}
    friend bool operator<(P a, Pb)
    {
    if(a.out == b.out) returna.sum > b.sum;
    return a.out > b.out;
    }
};

然后

priority_queue<P> q;

修改后的请看代码。

 

#include<queue>//栈
#include<string.h>//memset
#include<stdio.h>
using namespace std;

typedef long long ll;
typedef unsigned int uint;
typedef unsigned long long ull;

const int maxn = 4000 + 5;
const int maxm = 40000 + 5;
const ll maxd = 1e16 + 5;

struct Edge//结构体储存邻接表
{
    int to, next, mark;
    ll dis;
};
Edge graph[maxm * 2];

struct P
{
    int to;
    ll out;
    ll sum;
   // P() {}
    P(int _to, ll _out, ll _sum) : to(_to), out(_out), sum(_sum) {}//用priority_queue 时 要 初始化列表
};


/*priority_queue 对于基本类型的使用方法相对简单。他的模板声明带有三个参数:
priority_queue<Type, Container, Functional>
其中Type 为数据类型, Container 为保存数据的容器,Functional 为元素比较方式。*/


struct cmp//priority_queue 元素比较方式
{
    bool operator()(P a, P b)
    {
        if(a.out == b.out)
            return a.sum > b.sum;
        return a.out > b.out;
    }
};
int n, m, p;

int head[maxn], num;//head用于储存邻接表

int a, b;
ll d;

char str[3];//I or O

ll outd[maxn];//外径
ll sumd[maxn];//路径总和


void adg(int u, int v, ll dis, int mark)//邻接表
{
    graph[num].dis = dis;
    graph[num].to = v;

    graph[num].next = head[u];
    graph[num].mark = mark;
    head[u] = num++;
}

void dij(int s)
{
    /*fill函数的作用是:将一个区间的元素都赋予val值。
    函数参数:fill(first,last,val);
    first为容器的首迭代器,last为容器的末迭代器,val为将要替换的值。*/
    fill(outd, outd + n, maxd);//DIJ算法一开始先要把路径全设为无穷大
    fill(sumd, sumd + n, maxd);//同上
    priority_queue <P,vector<P>,cmp> q;//定义一个栈q,他的参数为P

    while(!q.empty()) q.pop();//清空队列
    outd[s] = sumd[s] = 0;//清空

    q.push(P(s, 0, 0));//第一个点入队

    while(!q.empty())
    {
        P p = q.top();//提取最小
        q.pop();//出队
        int u = p.to;//最小的那个数据
        for(int i = head[u]; i != -1; i = graph[i].next)//邻接表
        {
            int v = graph[i].to;//所连接的数据
            if(graph[i].mark == 0)//I
            {
                if(outd[v] > outd[u])//有里面走的话 外路径照搬 如果外径是小于号 证明有另一条内径的路通往那里
                {
                    sumd[v] = sumd[u] + graph[i].dis;//内径(总路径)加起来
                    outd[v] = outd[u];//外径照搬
                    q.push(P(v, outd[v], sumd[v]));//入队
                }
                else if(outd[v] == outd[u] && sumd[v] > sumd[u] + graph[i].dis)//如果外径一样 但内径更小
                {
                    sumd[v] = sumd[u] + graph[i].dis;//内径(总路径)加起来
                    outd[v] = outd[u];//外径照搬
                    q.push(P(v, outd[v], sumd[v]));//入队
                }
            }
            else//O
            {
                if(outd[v] > outd[u] + graph[i].dis)//输入的是O的话 那么就和普通DIJ做法没什么区别
                {
                    outd[v] = outd[u] + graph[i].dis;
                    sumd[v] = sumd[u] + graph[i].dis;//但要把总路径也加起来
                    q.push(P(v, outd[v], sumd[v]));
                }
                else if(outd[v] == outd[u] + graph[i].dis && sumd[v] > sumd[u] + graph[i].dis)//两者外径相等 要看谁总路径比较小
                {
                    outd[v] = outd[u] + graph[i].dis;
                    sumd[v] = sumd[u] + graph[i].dis;
                    q.push(P(v, outd[v], sumd[v]));
                }
            }
        }
    }
}


int main()
{

    scanf("%d%d%d", &n, &m, &p);
    num = 0;
    memset(head, -1, sizeof(head));


    for(int i = 0; i < m; i++)//把数据用邻接表储存起来
    {
        scanf("%d%d%lld%s", &a, &b, &d, str);

        if(str[0] == 'I')
        {

            adg(b, a, d, 0);
            adg(a, b, d, 0);
        }
        else
        {

            adg(b, a, d, 1);
            adg(a, b, d, 1);
        }
    }


    for(int i = 1; i <= p; i++)
    {
        scanf("%d%d", &a, &b);

        dij(a);

        if(sumd[b] == maxd)
            printf("IMPOSSIBLE\n");
        else
            printf("%lld %lld\n", outd[b], sumd[b]);
    }

    return 0;
}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值