问题描述
试题编号: |
201712-4 |
试题名称: |
行车路线 |
时间限制: |
1.0s |
内存限制: |
256.0MB |
问题描述: |
问题描述 小明和小芳出去乡村玩,小明负责开车,小芳来导航。 输入格式 输入的第一行包含两个整数n, m,分别表示路口的数量和道路的数量。路口由1至n编号,小明需要开车从1号路口到n号路口。 输出格式 输出一个整数,表示最优路线下小明的疲劳度。 样例输入 6 7 样例输出 76 样例说明 从1走小道到2,再走小道到3,疲劳度为52=25;然后从3走大道经过4到达5,疲劳度为20+30=50;最后从5走小道到6,疲劳度为1。总共为76。 数据规模和约定 对于30%的评测用例,1 ≤ n ≤ 8,1 ≤ m ≤ 10; |
------------------------------------------------------------
思路
Floyd + SPFA 求解最短路变形题。
问题的难点在于连续走小路L1,L2时,产生的疲劳值是 (L1 + L2)2 而不是 L12 + L22.
解决思路是把大路和小路分开在两张图考虑。由于小路的疲劳值是连续计算再平方的,所以首先用一个Floyd算法计算小路图的点对点最短路。如果直接用最普通的4行Floyd法O(n3)会超时,可以利用小路图矩阵的对称性优化一下常数,就不会超时了。
计算最短路的时候,Dijkstra算法及其变形(堆优化)还是Bellman-Ford算法及其变形(SPFA),都有一个d数组表示每个点到源的距离。此时因为走完大路可以再走大路或小路,但走完小路只能走大路(连续走小路的情形已被Floyd算法过程归化),因此要开两个数组db和ds分别表示最后一步走大路/小路时各点到源的距离,并按照上述原则在松弛操作中更新db和ds. 由于每个点i到源对应有两个距离db[i]和ds[i],db[i]成为全局最小后ds[i]仍有可能被更新变成全局最小,同理ds[i]成为全局最小后db[i]仍有可能被更新得到全局最小,因此基于全局最小距离进行松弛操作更新d数组的Dijkstra算法要修改适应本问题就比较困难。而Bellman-Ford算法及SPFA只用了相邻节点之间的路径信息,并不需要求全局最小,就可以适用于本问题。
还有一个造成笔者多次70分的问题是小路图和大路图不能合并成一张图(后来是看了别人的博客才想到的),原因还是之前提到的小路和大路的性质和影响是不同的。
坑点:
1. 虽然题目说了“保证答案不超过106”,但是中间过程可能会超过int范围,因此与距离有关的量都要用long long
2. 输入有重边
------------------------------------------------------------
代码
#define _CRT_SECURE_NO_WARNINGS
#include<cstdio>
#include<algorithm>
#include<queue>
#include<cstring>
#include<ctime>
// 涉及距离的量都要用long long防止中间结果溢出int范围
const int NMAX = 505;
const long long int INF = 0x3f3f3f3f3f3f3f3f;
int n ,m; // 点/边数
long long int mat[NMAX][NMAX] = {}; // 两点之间的疲劳距离
long long int small[NMAX][NMAX] = {}; // 两点之间的小路距离
long long int db[NMAX] = {}; // 最后一条路是大路情况下每个点到1号点的最短距离
long long int ds[NMAX] = {}; // 最后一条路是小路情况下每个点到1号点的最短距离
bool vis[NMAX] = {}; // 节点是否在spfa队列中
void spfa(int st) // spfa求最短路(分db和ds),以st为源
{
int i, u;
bool flag; // 是否入队标记
memset(db, 0x3f, sizeof(db)); // 初始化各点到源的距离为无穷大
memset(ds, 0x3f, sizeof(ds));
db[st] = 0; // 初始化到源的距离为0
ds[st] = 0;
std::queue<int> q; // 节点队列
q.push(st); // 源入队
vis[st] = 1; // 标记源入队
while (!q.empty()) // 当队列不空时循环
{
u = q.front(); // 取队首节点
q.pop(); // 队首节点出队
vis[u] = 0; // 标记队首节点出队,之后可能的话还可以再入队
for (i=0; i<n; i++)
{
flag = false;
if (small[u][i] < INF) // 有小路连通
{
if (db[u] + small[u][i]*small[u][i] < ds[i]) // 如果i和u有道路相连且可以更新i到源的距离
{
ds[i] = db[u] + small[u][i]*small[u][i]; // 更新i到源的距离
flag = true;
}
}
if (mat[u][i] < INF) // 有大路连通
{
if (db[u] + mat[u][i] < db[i] || ds[u] + mat[u][i] < db[i])
{
db[i] = std::min(db[u], ds[u]) + mat[u][i];
flag = true;
}
}
if (!vis[i] && flag)
{
vis[i] = 1;
q.push(i);
}
}
}
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("CCF201712-4_s.txt", "r", stdin);
#endif
//clock_t t1, t2;
int i, j, k, f, s1, s2;
long long int l;
bool b_flag = 0, s_flag = 0; // 大路/小路最短路有没有访问到n号点
scanf("%d%d", &n, &m);
memset(mat, 0x3f, sizeof(mat));
memset(small, 0x3f, sizeof(small));
for (i=0; i<m; i++)
{
scanf("%d%d%d%lld", &f, &s1, &s2, &l);
s1--;
s2--;
if (f)
{
small[s1][s2] = std::min(l, small[s1][s2]);
small[s2][s1] = small[s1][s2];
}
else
{
mat[s1][s2] = std::min(l, mat[s1][s2]);
mat[s2][s1] = mat[s1][s2];
}
}
// Floyd算法求小路的所有连通支(矩阵对称性优化,使Floyd算法的时间复杂度近似变为一半)
for (i=0; i<n; i++)
for (j=0; j<n; j++)
for (k=j+1; k<n; k++)
{
small[j][k] = std::min(small[j][i] + small[i][k], small[j][k]);
small[k][j] = small[j][k];
}
// SPFA, 将最后一步通过大路达到的距离和最后一步通过小路达到的距离分开讨论
spfa(0);
printf("%lld\n", std::min(db[n-1], ds[n-1]));
return 0;
}