P4768 [NOI2018] 归程 解题报告

本文介绍了如何运用Kruskal重构树和最短路算法解决一道图论题目,该题目涉及寻找在满足特定条件下的最短路径。通过构建Kruskal树并进行最短路预处理,然后根据限制条件确定中转点,最终求得第二部分路径的最小长度。解题过程包括理解题目需求、转化问题、应用Kruskal算法和最短路算法,以及代码实现。
摘要由CSDN通过智能技术生成

P4768 [NOI2018] 归程 解题报告

tag:图论(Kruskal重构树,树上倍增,最短路)

题目链接

https://www.luogu.com.cn/problem/P4768

题目大意

有一个 n n n m m m 边的无向连通图,每条边 i i i 有个长度 l i l_i li 和海拔 a i a_i ai Q Q Q次询问,每次给出一个起始点 v v v 和 水位线 p p p,请回答这样一个问题:

现在我要从 v v v 点去 1 1 1 点,要选择一条路径 P a t h ( v , 1 ) \mathrm{Path}(v,1) Path(v,1) 。这个路径由两部分组成: 第一部分 开车,第二部分 走路。第一部分路径上所有的边要求满足 a i > p a_i>p ai>p。求第二部分路径长度和的最小值。

换而言之,在所有路径 P a t h ( v , 1 ) \mathrm{Path}(v,1) Path(v,1) 中选择一条,并确定一个 “中转点” u u u,要求满足对任意边 e ∈ P a t h ( v , u ) e\in\mathrm{Path}(v, u) ePath(v,u),有 a e > p a_e>p ae>p。最小化 ∑ e ∈ P a t h ( u , 1 ) l e \sum_{e\in\mathrm{Path}(u,1)}l_e ePath(u,1)le.

  • n ≤ 2 × 1 0 5 , m ≤ 4 × 1 0 5 , Q ≤ 4 × 1 0 5 n\le 2\times 10^5,m\le 4\times 10^5,Q\le 4\times 10^5 n2×105,m4×105,Q4×105.
  • 多组数据,强制在线

(又是什么鬼转化)

解题思路

读完题后,我们发现,只要确定了 u u u ,那么 P a t h ( v , u ) \mathrm{Path}(v,u) Path(v,u) 可以轻松解决,用最短路预处理即可。

那么问题就转化为,已知 v v v 和限制 p p p,求仅通过 a i > p a_i>p ai>p 的边能到达哪些点。

这是一个 Kruskal重构树 的经典问题。详细请看这篇博客。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
typedef long long ll;
char In[1 << 20], *ss = In, *tt = In;
#define getchar() (ss == tt && (tt = (ss = In) + fread(In, 1, 1 << 20, stdin), ss == tt) ? EOF : *ss++)
ll read() {
	ll x = 0, f = 1; char ch = getchar();
	for(; ch < '0' || ch > '9'; ch = getchar()) if(ch == '-') f = -1;
	for(; ch >= '0' && ch <= '9'; ch = getchar()) x = x * 10 + int(ch - '0');
	return x * f;
}
/*
 * note:
 * n<=2e5
 * m<=4e5
 * Q<=4e5
 * may be boom int(edge weight)
 */
const int MAXN = 4e5 + 5, MAXM = 8e5 + 5, iINF = 0x3f3f3f3fll;
const ll lINF = 0x3f3f3f3f3f3f3f3fll;
int n, m, Q, K, S;
ll lans;
struct Graph {
	int ver[MAXM], nxt[MAXM], edg[MAXM], alt[MAXM], head[MAXN], cnt;
	void addedge(int u, int v, int w, int a) {
		ver[++cnt] = v; edg[cnt] = w; alt[cnt] = a; nxt[cnt] = head[u]; head[u] = cnt;
	}
	void clear() {
		memset(head, 0x00, sizeof head); cnt = 0;
	}
};
namespace SP {//Shortest Path
	struct QN {
		ll d; int u;
		bool operator < (const QN& b)const {return d > b.d;}
	};
	Graph g;
	ll dist[MAXN];
	int vis[MAXN];
	priority_queue<QN> pq;
	void work() {
		memset(dist, 0x3f, sizeof dist);
		dist[1] = 0; pq.push((QN){0ll, 1});
		while(pq.size()) {
			int u = pq.top().u; pq.pop();
			if(vis[u]) continue;
            vis[u] = 1;
			for(int i = g.head[u]; i; i = g.nxt[i]) {
				int v = g.ver[i], w = g.edg[i];
				if(dist[v] > dist[u] + w) {
					dist[v] = dist[u] + w;
					pq.push((QN){dist[v], v});
				}
			}
		}

	}
}
namespace KT {//Kruskal Tree
	int upto[MAXN], num, val[MAXN], leaf[MAXN], rt, fa[MAXN][20];
	ll mind[MAXN];
	struct Edge {int u, v, w, a;}e[MAXM];
	Graph tr;
	bool cmp(const Edge& A, const Edge& B) {return A.a > B.a;}
	int getup(int u) {return u == upto[u] ? u : upto[u] = getup(upto[u]);}
	void dfs(int u, int f) {
		fa[u][0] = f; mind[u] = leaf[u] ? SP::dist[u] : lINF;
		for(int k = 1; k < 20; k++) fa[u][k] = fa[fa[u][k-1]][k-1];
		for(int i = tr.head[u]; i; i = tr.nxt[i]) if(tr.ver[i] != f) {
			int v = tr.ver[i];
			dfs(v, u);
			mind[u] = min(mind[u], mind[v]);
		}
	}
	void build() {
		num = n;
		sort(e + 1, e + 1 + m, cmp);
		for(int i = 1; i <= n; i++) upto[i] = i, leaf[i] = 1, val[i] = iINF;
		for(int i = 1; i <= m; i++) {
			int fu = getup(e[i].u), fv = getup(e[i].v);
			if(fu == fv) continue;
			val[++num] = e[i].a; upto[num] = upto[fu] = upto[fv] = num;
			tr.addedge(fu, num, 0, 0); tr.addedge(num, fu, 0, 0);
			tr.addedge(fv, num, 0, 0); tr.addedge(num, fv, 0, 0);
		}
		rt = getup(1);
		dfs(rt, 0);
	}
	int query(int u, int p) {
		for(int i = 19; i >= 0; i--)
			if(fa[u][i] && val[fa[u][i]] > p)
				u = fa[u][i];
		return u;
	}
}
void clear_all();
int main() {
	int T = read();
	while(T--) {
		clear_all();
		n = read(), m = read();
		for(int i = 1; i <= m; i++) {
			int u = read(), v = read(), w = read(), a = read();
			KT::e[i] = (KT::Edge){u, v, w, a};
			SP::g.addedge(u, v, w, a); SP::g.addedge(v, u, w, a);
		}
		SP::work();
		KT::build();
		Q = read(); K = read(); S = read();
		for(int i = 1; i <= Q; i++) {
			int v = (read() + K * lans - 1) % n + 1, p = (read() + K * lans) % (S + 1);
			int t = KT::query(v, p);
			lans = KT::mind[t];
			printf("%lld\n", lans);
		}
	}
	return 0;
}

void clear_all() {
	n = m = Q = K = S = 0;
	lans = 0;
	using namespace SP;
	g.clear();
	memset(dist, 0x00, sizeof dist);
	memset(vis, 0x00, sizeof vis);
	while(pq.size()) pq.pop();
	using namespace KT;
	memset(upto, 0x00, sizeof upto);
	num = 0;
	memset(val, 0x00, sizeof val);
	memset(leaf, 0x00, sizeof leaf);
	rt = 0;
	memset(fa, 0x00, sizeof fa);
	memset(mind, 0x00, sizeof mind);
	tr.clear();	
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

日居月诸Rijuyuezhu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值