Description
城市路网:用有向图G(V,E)表示城市路网,其中V={v1,v2,…,vm}表示m个路口结点,有向边 e(vi,vj)表示连接路口vi和vj的路段。不存在歧义的情况下,我们将e(vi,vj)缩写为e。
路段行驶时间:给定路段e,首先定义该路段为空载时(即没有车辆通过)的行驶时间为t0(e)。然后,定义路段e上的流量fe为所有通过路段e的车辆总和。给定路段e以及流量f_e,每辆车通过路段e的行驶时间t(e)定义为:
t(e) = t0(e) * (1 + α(e) * (fe - 1))
其中α(e)为路段e的拥堵参数,与该路段的属性(譬如路段限速,路段容量)相关。
路径和路径行驶时间:定义一条路径π是由一系列路口结点(v1,v2,…,vk)顺序组成。用T(π)表示路径π的行驶时间,定位为其路径上每段路段的行驶时间之和:
其中路径π通过路段e(vi,v(i+1)),当且仅当在路径π上,存在连续的的路口结点vi和v(i+1),使得 e(vi,v(i+1))属于E。
路径规划问题:给定城市路网G(V,E),以及导航请求集合Q={q1,q2,…,qn}。该集合包含n个导航请求,每个导航请求qi=(si,di),其中si表示出发路口结点,di表示目的地路口结点。路径规划问题需要为每个导航请求qi规划一条从si到di的路径πi,使得所有车辆在该组合路径Π={π1,π2,…,πn}下的行驶时间之和最小。路网所有车辆的行驶时间之和定义为:
大致思路: 在首次看完题目之后,我们的大致思路是使用迪杰斯特拉算法来搜索最短路径。因为每个请求都是基于前一个执行后的请求更新之后的流量图来进行最短用时路径的计算的,所以确实是每次求当前最优(即用dijkstra),之后再根据求出的最短路径更新流量图。值得注意的是,题目没有说明节点之间是否存在重边的情况。在我们计算最短时间时,需要假设将每条道路的流量都加一,把走过每个路段所需时间计算得到并进行比较,选择花费时间最短的路径。
版本1.0(通过测试样例,但超时):
在这个版本中,我们按大致思路进行实现。我们首先定义了Node、Edge、Request三个结构分别代表问题中的路口节点、有向路段、导航请求。
同时我们用了一个二维数组表示当前预测流量图(即每边加1的情况),二维向量来存储当前实际流量图(因为当时考虑到重边个数无法确定所以采用向量形式来保存),每次更新后再由二维向量生成新的二维数组(从向量的重边中取t最小的边来构造)。
之后声明了time函数用于计算每个路段所花费的时间。
在main函数中,我们首先进行输入,完成对城市地图的初始化并假设每条道路的流量加一,以便寻找最短时间路径。
之后使用普通的迪杰斯特拉算法从起始点开始不断寻找最短时间路径。并且使用一个数组pre来记录所经过的节点的前一个节点。再使用栈trace沿着pre循环一次来逆向保存路径。至此,我们成功输出了我们所求得的最短时间路径,但是当我们把所写代码提交平台时,测试结果总是超时。
遇到的最大问题:超时,此时我们在算法的各个部分寻找可以进行优化的时间复杂度的地方,我们发现我们当前所使用的普通的迪杰斯特拉算法时间复杂度为O(n²),在这里我们进行改进,使用堆优化的迪杰斯特拉算法,将其时间复杂度降低为O(n*log(n))。
版本2.0(平台测试Accepted不超时版):
在新的版本中我们只是对我们所使用的迪杰斯特拉算法进行了堆优化,同时将用于保存当前流量的二维向量改为了二维数组来提升运算速度,并且去除了用于dijkstra算法的保存当前预测流量使用的二维数组atime_map(这样可能会导致没有考虑到重边的存在但是也加快了运算速度)。
使用堆优化的迪杰斯特拉算法寻找最短时间路径,创建priority_queue que,并构建链式向前星函数add:
最终程序代码如下
#include <iostream>
#include<cmath>
#include <algorithm>
#include<vector>
#include<queue>
#include<string.h>
using namespace std;
const float maxdis = 10000000;
int head[400], cnt = 0;
struct Request
{
int s; int e;
};
struct Edge
{
float a, t,f;
int v,i,next;
}edge[20000];
Request q[8001];
float time(float t, float a, float f)
{
if (t == maxdis)return maxdis;
else
{
float result = t * (1 + a * (f - 1));
return result;
}
}
void add(int u, int v, float a, float t, int f, int i)
{
edge[++cnt].v = v;
edge[cnt].a = a;
edge[cnt].t = t;
edge[cnt].f = f;
edge[cnt].i = i;
edge[cnt].next = head[u];
head[u] = cnt;
}
struct Node
{
float dis;int pos;
friend bool operator <(Node a, Node b)
{
return a.dis > b.dis;
}
};
priority_queue<Node>que;
int main()
{
int T = 0;
cin >> T;
while (T--)
{
int m, E, n, s, t, u, v; cnt = 0;
float t0, a1, a2;
cin >> m >> E >> n;
memset(head, -1, sizeof(head));
for (int i = 0; i < n; i++)
{
cin >> s >> t;
q[i].s = s;
q[i].e = t;
}
for (int i = 0; i < E; i++)
{
cin >> u >> v >> t0 >> a1 >> a2;
add(u, v, a1, t0, 0, 2 * i);
add(v, u, a2, t0, 0, 2 * i+1);
}
float dis[401]; int pre[401]; int trace[1000];
for (int i = 0; i < n; i++)
{
bool visit[401]; int start = q[i].s; int termination = q[i].e; int num = 0; int map[400][400];//pre用于记录节点的前一个节点,trace用于记录路径
memset(visit, false, sizeof(visit)); memset(map, -1, sizeof(map));
for (int j = 0; j < m; j++)
{
dis[j] = maxdis;
}//初始化dis和pre
dis[start] = 0;
Node z;
z.dis = 0;
z.pos=start;
que.push(z);
while (!que.empty())
{
Node node = que.top();
que.pop();
int id = node.pos;
if (visit[id])continue;
visit[id] = 1;
for (int j = head[id]; j!=-1; j = edge[j].next)
{
float p = time(edge[j].t, edge[j].a, edge[j].f + 1);
int eid = edge[j].v;
if (dis[eid] > dis[id] + p)
{
dis[eid] = dis[id] + p;
pre[eid] = id; map[id][eid] = j;
Node ann; ann.dis = dis[eid]; ann.pos = eid;
que.push(ann);
}
}
}
//打印
int temp = termination;
while (temp != start)
{
trace[num]=temp;
num++;
trace[num]=edge[map[pre[temp]][temp]].i;
num++;
//更新
edge[map[pre[temp]][temp]].f++;
edge[map[pre[temp]][temp]].f++;
temp = pre[temp];
}
trace[num] = start;
num++;
cout << num << endl;
for (int j = num-1; j >=0; j--)
{
cout << trace[j] << " ";
}
cout << endl;
}
}
return 0;
}