【题解】[JLOI2011] 飞行路线
题目信息
题目来源:CCF 吉林省选 2011;
在线评测地址:Luogu#4568,BZOJ#2763;
运行限制:时间不超过 (1.00 extrm{ms}),空间不超过 (128 extrm{MiB})。
题目描述
Alice 和 Bob 现在要乘飞机旅行,他们选择了一家相对便宜的航空公司。该航空公司一共在 (n) 个城市设有业务,设这些城市分别标记为 (0) 到 (n−1),一共有 (m) 种航线,每种航线连接两个城市,并且航线有一定的价格。
Alice 和 Bob 现在要从一个城市沿着航线到达另一个城市,途中可以进行转机。航空公司对他们这次旅行也推出优惠,他们可以免费在最多 (k) 种航线上搭乘飞机。那么 Alice 和 Bob 这次出行最少花费多少?
输入格式
第一行三个整数 (n)、(m)、(k),分别表示城市数,航线数和免费乘坐次数。
接下来一行两个整数 (s)、(t),分别表示他们出行的起点城市编号和终点城市编号。
接下来 (m) 行,每行三个整数 (a)、(b)、(c),表示存在一种航线,能从城市 (a) 到达城市 (b),或从城市 (b) 到达城市 (a),价格为 (c)。
输出格式
输出一行一个整数,为最少花费。
数据规模与约定
- 对于 (30\%) 的数据,(2le nle 50),(1le mle 300),(k=0);
- 对于 (50\%) 的数据,(2le nle 600),(1le mle 6 imes 10^3),(0le kle 1);
- 对于 (100\%) 的数据,(2le nle 10^4),(1le mle 5 imes 10^4),(0le kle 10)。
保证 (0le s,t,a,ble n),(a e b),(0le cle 10^3)。
分析
题意很明确,给定一张图,一条路径上最多可以使 (k) 条边权值为 (0),求最短路。
请注意这道题与 [USACO18JAN-Sliver] Telephone Lines 的区别。这道题的最短路是求和,而那一道题的最短路则是取最大值。这两个区别导致这两题本质不同。
由于有用了多少条边的信息,我们可以构造分层图。第 (i) 层((0le ile n))为用了 (i) 次使边权为 (0) 的操作。
只需要使起点为 ((s,0)),求解 ((t,0),(t,1),cdots ,(t,k)) 的最短路并去最小值即可。
注意事项
这道题的图是无向图,注意加双向边,同时开两倍空间。
Code
图论题,尤其是最短路的代码还是比较好敲的,注意一下细节就行了。
#include <queue>
#include <cstdio>
#include <cctype>
#include <cstring>
using namespace std;
const int max_n = 10000, max_m = 50000, max_k = 10, max_l = max_k + 1;
struct node
{
int id, val;
node(int _i = 0, int _v = 0) : id(_i), val(_v) { }
bool operator<(const node& n) const
{
return val > n.val;
}
};
int hd[max_n*max_l], des[max_m*(max_k+max_l)*2], val[max_m*(max_k+max_l)*2], nxt[max_m*(max_k+max_l)*2], e_cnt = 0; // 存图(2*空间)
int dis[max_n*max_l], n;
bool vis[max_n*max_l] = {};
priority_queue<node> q;
inline int mk(int id, int ly) { return id + ly * n; } // 生成节点编号
#define gc getchar
inline int read() // 快读
{
int c = gc(), t = 1, n = 0;
while (isspace(c)) { c = gc(); }
if (c == '-') { t = -1, c = gc(); }
while (isdigit(c)) { n = n * 10 + c - '0', c = gc(); }
return n * t;
}
#undef gc
void add_edge(int s, int t, int v)
{
des[e_cnt] = t, val[e_cnt] = v;
nxt[e_cnt] = hd[s], hd[s] = e_cnt++;
}
int main()
{
memset(hd, -1, sizeof(hd));
memset(dis, 0x3f, sizeof(dis));
n = read();
int m = read(), k = read(), s = read(), t = read(), ta, tb, tc, ans = 0x3f3f3f3f;
node cur;
for (int i = 0; i < m; i++)
{
ta = read(), tb = read(), tc = read();
for (int j = 0; j < k; j++)
{
add_edge(mk(ta, j), mk(tb, j), tc); // 层内的边
add_edge(mk(tb, j), mk(ta, j), tc);
add_edge(mk(ta, j), mk(tb, j+1), 0); // 使用一次操作,跨层
add_edge(mk(tb, j), mk(ta, j+1), 0);
}
add_edge(mk(ta, k), mk(tb, k), tc); // 注意总共有 k+1 层
add_edge(mk(tb, k), mk(ta, k), tc);
}
q.push(node(mk(s, 0), 0));
dis[mk(s,0)] = 0;
while (!q.empty()) // 最短路
{
cur = q.top();
q.pop();
if (!vis[cur.id])
{
vis[cur.id] = true;
for (int p = hd[cur.id]; p != -1; p = nxt[p])
if (dis[des[p]] > dis[cur.id] + val[p])
{
dis[des[p]] = dis[cur.id] + val[p];
if (!vis[des[p]])
q.push(node(des[p], dis[des[p]]));
}
}
}
for (int i = 0; i <= k; i++)
if (dis[mk(t,i)] < ans)
ans = dis[mk(t, i)]; // 统计最小答案
printf("%d
", ans); // 输出
return 0; // 然后就 AC 了、
}