题意
不久后滑铁卢将会变得非常冷,但是幸运的是,很多建筑都被桥梁和隧道连接着,所以你不需要总是走在外面。但是现在建筑
物之间的连接是错综复杂的,很难知道某两个建筑物之间的最优路线,所以需要你写程序判断。
给出 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;
}