『图论建模·最短路/生成树』[AMPPZ2014]Petrol

P r o b l e m \mathrm{Problem} Problem

给定一个n个点、m条边的带权无向图,其中有s个点是加油站。
每辆车都有一个油量上限b,即每次行走距离不能超过b,但在加油站可以补满。
q次询问,每次给出x,y,b,表示出发点是x,终点是y,油量上限为b,且保证x点和y点都是加油站,请回答能否从x走到y。


S o l u t i o n \mathrm{Solution} Solution

如果我们将数据范围改变到 O ( n 2 ) O(n^2) O(n2)级别的话,我们可以将每一个加油站之间按照最短路径为边权,两两连边。在询问时我们只需要找到两点间最大边权值,与给定的数值进行比较即可。

要求任意两点间最大边权最小,所求即为瓶颈生成树,就是最小生成树。

但是我们显然无法做到对于任意两个加油站之间都有连边,因此我们需要找到某一种方法来优化建图。考虑 x x x y y y最短路径中的某一个点 p p p.
『图论建模·最短路/生成树』[AMPPZ2014]Petrol_第1张图片
距离点 p p p最近的加油站不是 x x x y y y,设该点为 c c c,则 max ⁡ ( x , c ) , max ⁡ ( y , v ) ≤ max ⁡ ( x , y ) . \max(x,c),\max(y,v)\le \max(x,y). max(x,c),max(y,v)max(x,y).那么 x x x y y y的边完全可以通过 ( x , c ) (x,c) (x,c)以及 ( c , y ) (c,y) (c,y)
来经过.

  • 至少路劲长度是一样的,因为边 ( p , c ) (p,c) (p,c)不会对路劲产生影响。
  • 除此之外,还能使 ( x , c ) (x,c) (x,c) ( y , c ) (y,c) (y,c)更优。
  • p p p还能起到中转站的作用,避免了 x x x y y y的直接连边,减小建图复杂度。

若离 p p p最近的节点是 x x x或是 y y y,那么 ( x , p ) (x,p) (x,p) ( p , y ) (p,y) (p,y)依然没有影响。

因此,我们若向每一个节点相邻的最近的点去建边的话,就可以代替任意两点之间的连边了。对于边 ( x , y , v ) (x,y,v) (x,y,v),我们建边为 ( min ⁡ x , min ⁡ y , d i s x + d i s y + v ) (\min_x,\min_y,dis_x+dis_y+v) (minx,miny,disx+disy+v). min ⁡ x \min_x minx表示节点 x x x所能到达的最近的加油站, d i s x dis_x disx表示节点 x x x min ⁡ x \min_x minx的距离。

用上述边权建立最小生成树即可。

最小生成树求完以后不需要使用LCA,直接用离线处理即可。


C o d e \mathrm{Code} Code

#include 
#define int long long

using namespace std;
const int N = 8e5;

int n, m, s, k, Q;
pair< int, pair<int,int> > e[N * 2];
int dis[N], path[N], key[N], vis[N], fa[N], ans[N]; 
vector < pair<int,int> > G[N];
struct node { int x, y, v, id; } q[N];

int read(void)
{
	int s = 0; char c = getchar();
	while (c < '0' || c > '9') c = getchar();
	while (c >= '0' && c <= '9') s = s * 10 + c - 48, c = getchar();
	return s; 
}

void Dijkstra(void)
{
	priority_queue < pair<int,int> > q;
	memset(vis, 0, sizeof vis);
	memset(dis, 30, sizeof dis);
	for (int i=1;i<=s;++i)
	{
		 dis[key[i]] = 0;
		 path[key[i]] = key[i];
		 q.push({0, key[i]});
	}
	while (q.size())
	{
		int x = q.top().second; q.pop();
		if (vis[x]) continue; vis[x] = 1;
		for (int i=0;i<G[x].size();++i)
		{
			int y = G[x][i].first;
			int v = G[x][i].second;
			if (dis[x] + v < dis[y]) {
				dis[y] = dis[x] + v;
				path[y] = path[x];
				q.push({-dis[y], y});
			}
		}
	}
	return;
}

int get(int x) {
	if (fa[x] == x) return x;
	return fa[x] = get(fa[x]);
}

bool Pig(node p1, node p2) {
	return p1.v < p2.v;
}

void Kruscal(void)
{
	for (int i=1;i<=n;++i) fa[i] = i;
	sort(e + 1, e + k + 1);
	sort(q + 1, q + Q + 1, Pig);
	int j = 1;
	for (int i=1;i<=Q;++i)
	{
		while (e[j].first <= q[i].v and j <= k)
		{
			int x = e[j].second.first;
			int y = e[j].second.second;
			fa[get(x)] = get(y), j ++;
		}
		ans[q[i].id] = get(q[i].x) == get(q[i].y);
	}
	return;
} 

signed main(void)
{
	n = read(), s = read(), m = read();
	for (int i=1;i<=s;++i) key[i] = read();
	for (int i=1;i<=m;++i)
	{
		int x = read(), y = read(), v = read();
		G[x].push_back({y, v});
		G[y].push_back({x, v});
	}	
	Dijkstra();
	for (int x=1;x<=n;++x) {
		for (int i=0;i<G[x].size();++i)
		{
			int y = G[x][i].first;
			int v = G[x][i].second + dis[x] + dis[y];
			if (y > x) continue;
			if (path[x] == path[y]) continue;
			e[++ k] = {v, {path[x], path[y]}};
		}
	}
	Q = read();
	for (int i=1;i<=Q;++i) {
		int x = read(), y = read(), v = read();
		q[i] = {x, y, v, i};
	}
	Kruscal();
	for (int i=1;i<=Q;++i) puts(ans[i] ? "TAK" : "NIE");
	return 0;
} 

你可能感兴趣的:(『图论建模·最短路/生成树』[AMPPZ2014]Petrol)