【题解】[JLOI2011] 飞行路线

题目信息

题目来源:CCF 吉林省选 2011;

在线评测地址:Luogu#4568BZOJ#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 了、
}