2023 hdu 第1场 1001 Hide-And-Seek Game

文章描述了一个关于在具有边权的树中,两个路径Serenade和Rhapsody的最早相遇节点查询问题。通过计算节点间的距离和使用扩展欧几里得算法确定步数,解决在给定节点集合上的相遇节点查找问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Problem Description

在一棵树上,树的边权为 111SerenadeSerenadeSerenadeRhapsodyRhapsodyRhapsodya 从各自的起点 SaS_aSa , SbS_bSb 向着终点 TaT_aTa , TbT_bTb 做往返运动。

SerenadeSerenadeSerenadeRhapsoyaRhapsoyaRhapsoya 在节点上的最早相遇节点。

Input

第一行输入一个整数 ttt (1≤t≤500)(1 \le t \le 500)(1t500) ,表示测试组数。

接下来一行输入两个整数 n,mn,mn,m (2≤n,m≤3×103)(2 \le n,m \le 3 \times 10^3)(2n,m3×103),表示的树的节点个数和查询次数。

接下来 n−1n-1n1 行每行输入两个整数 u,vu,vu,v (1≤u,v≤n,u≠v)(1 \le u,v \le n,u\not= v)(1u,vn,u=v) ,表示 u,vu,vu,v 之间存在一条边。

接下来 mmm 每行输入四个整数 Sa,Ta,Sb,TbS_a,T_a,S_b,T_bSa,Ta,Sb,Tb (1≤Sa,Ta,Sb,Tb≤n,Sa≠Ta,Sb≠Tb)(1 \le S_a,T_a,S_b,T_b \le n,S_a \not= T_a,S_b \not= T_b)(1Sa,Ta,Sb,Tbn,Sa=Ta,Sb=Tb) ,表示 SerenadeSerenadeSerenadeRhapsodyRhapsodyRhapsodya 各自的起点和终点。

数据保证不超过 202020n,mn,mn,m 超过 400400400

Output

对于每次查询,输出对应节点编号,如果不会相遇则输出 −1-11

Solution

下文中 SerenadeSerenadeSerenadeRhapsoyaRhapsoyaRhapsoya 都视作 aaa , bbb

首先如果 aaabbb 要相交,那么 aaa 起点到终点的路径必然和 bbb 起点到终点的路径相交。而对于相交路径,由于 nnnmmm 只有 3×1033 \times 10^33×103 ,我们可以直接暴力在 aaabbb 的路径上的点各自 +1+1+1 ,那么此时我们只需考虑路径上的权值为 222 的点即可。

那么对于一个点是否能相遇以及求 aaabbb 的最早相遇步数,我们进行分类讨论,设 al0,al1al_0,al_1al0,al1bl0,bl1bl_0,bl_1bl0,bl1aaabbb 起点和终点到交点距离,k1,k2∈N+k_1,k_2 \in N^+k1,k2N+,发现相遇只有 444 种情况:

· aaa 从起点走出与 bbb 从起点走出相遇。 2k1×(al0+al1)+al0=2k2×(bl0+bl1)+bl02k_1 \times (al_0+al_1) + al_0 = 2k_2 \times (bl_0+bl_1) + bl_02k1×(al0+al1)+al0=2k2×(bl0+bl1)+bl0

· aaa 从起点走出与 bbb 从终点走出相遇。 2k1×(al0+al1)+al0=(2k2−1)×(bl0+bl1)+bl12k_1 \times (al_0+al_1) + al_0 = (2k_2-1) \times (bl_0+bl_1) + bl_12k1×(al0+al1)+al0=(2k21)×(bl0+bl1)+bl1

· aaa 从终点走出与 bbb 与起点走出相遇。 (2k1−1)×(al0+al1)+al1=2k2×(bl0+bl1)+bl0(2k_1-1) \times (al_0+al_1) + al_1 = 2k_2 \times (bl_0+bl_1) + bl_0(2k11)×(al0+al1)+al1=2k2×(bl0+bl1)+bl0

· aaa 从终点走出与 bbb 与终点走出相遇。 (2k1−1)×(al0+al1)+al1=(2k2−1)×(bl0+bl1)+bl1(2k_1-1) \times (al_0+al_1) + al_1 = (2k_2-1) \times (bl_0+bl_1) + bl_1(2k11)×(al0+al1)+al1=(2k21)×(bl0+bl1)+bl1

对于如何计算最少步数和是否相交,我们只需 exgcdexgcdexgcd 算出 k1k_1k1k2k_2k2 的值,如何算对应最小整数即可。

复杂度分析:O(20nmlogn)O(20nmlogn)O(20nmlogn) ,表面一算复杂度甚至可能会到 2×1092 \times 10^92×109,但是观察到 exgcdexgcdexgcd 实际并不会达到 lognlognlogn ,虽然还带有可能 555 倍的常数(?),但是还是能神秘地通过此题。。。

Code

#include <bits/stdc++.h>
#define endl '\n'
using namespace std;
const int N = 3e3 + 10, M = 6e3 + 10;
int meet_point, step;//最优相遇交点和步数
vector<int>a(2), b(2), al(2), bl(2);
int acs[N];
int head[N], e[M], ne[M], idx;
int fa[N], dep[N], siz[N], wson[N], top[N];
void clear(int n) {
	idx = 0;
	for (int i = 0; i <= n + 5; i++) head[i] = -1, wson[i] = 0;
}
inline void addedge(int a, int b) {
	e[idx] = b, ne[idx] = head[a], head[a] = idx++;
}
int exgcd(int a, int b, int& x, int& y) {
	if (!b) {
		x = 1, y = 0;
		return a;
	}
	int d = exgcd(b, a % b, y, x);
	y -= (a / b) * x;
	return d;
}
//树剖LCA
void dfs1(int u, int father) {
	dep[u] = dep[father] + 1, fa[u] = father, siz[u] = 1;
	for (int i = head[u]; i != -1; i = ne[i]) {
		int v = e[i];
		if (v == father)continue;
		dfs1(v, u);
		if (siz[v] > siz[wson[u]])wson[u] = v;
		siz[u] += siz[v];
	}
}
void dfs2(int u, int chead) {
	top[u] = chead;
	if (wson[u])dfs2(wson[u], chead);
	for (int i = head[u]; i != -1; i = ne[i]) {
		int v = e[i];
		if (v == fa[u] || v == wson[u])continue;
		dfs2(v, v);
	}
}
int lca(int a, int b) {
	while (top[a] != top[b]) {
		if (dep[top[a]] < dep[top[b]])b = fa[top[b]];
		else a = fa[top[a]];
	}
	return dep[a] < dep[b] ? a : b;
}
void add_point(int u, int father) {//在u到father路径上的点+1
	while (u != father) {
		acs[u]++;
		u = fa[u];
	}
}
int dis(int x, int y) {//求x到y的距离
	int anc = lca(x, y);
	return dep[x] + dep[y] - 2 * dep[anc];
}
void judge_meet(int cross, int left, int right) {//判断是否相遇并统计答案
	int x0, y0, c, d, dx, dy;
	int lena = 2 * (al[0] + al[1]), lenb = 2 * (bl[0] + bl[1]);
	d = exgcd(lena, lenb, x0, y0);
	dx = lenb / d, dy = lena / d;
	c = right - left;
	if (c % d == 0) {
		int x = x0 * c / d, y = y0 * c / d;
		x = (x % dx + dx) % dx;
		y = (y % dy + dy) % dy;
		y -= dy;
		int val = min(lena * x + left, -lenb * y + right);
		if (step > val)meet_point = cross, step = val;
	}
}
void get_ans(int cross) {
	al[0] = dis(a[0], cross), al[1] = dis(a[1], cross);//al[0],al[1]表示a点起点终点到交点的距离
	bl[0] = dis(b[0], cross), bl[1] = dis(b[1], cross);//bl[0],bl[1]表示b点起点终点到交点的距离
	judge_meet(cross, al[0], bl[0]);
	judge_meet(cross, al[0], bl[0] + 2 * bl[1]);
	judge_meet(cross, al[0] + 2 * al[1], bl[0] + 2 * bl[1]);
	judge_meet(cross, al[0] + 2 * al[1], bl[0]);
}
void solve() {
	int n, m;
	cin >> n >> m;
	clear(n);
	for (int i = 1; i <= n - 1; i++) {
		int u, v;
		cin >> u >> v;
		addedge(u, v), addedge(v, u);
	}
	dfs1(1, 0);
	dfs2(1, 1);
	while (m--) {
		meet_point = -1;
		step = 2e9;
		for (int i = 1; i <= n; i++)acs[i] = 0;
		cin >> a[0] >> a[1] >> b[0] >> b[1];
		int x = lca(a[0], a[1]), y = lca(b[0], b[1]);
		add_point(a[0], x), add_point(a[1], x), acs[x]++;
		add_point(b[0], y), add_point(b[1], y), acs[y]++;
		for (int i = 1; i <= n; i++) if (acs[i] >= 2)get_ans(i);
		cout << meet_point << endl;
	}
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t;
	cin >> t;
	while (t--)solve();
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值