「泉州一中基地赛20180519」第四题题解 (代码注释有时间再补)

1.题目

2.分析

①操作一应该做的操作

我们可以知道,树上两点的路径 ( 路 径 上 的 点 的 集 合 记 作 l ) (路径上的点的集合记作l) (l) 是确定的,所以任意两个被标记的点之间的路径上的点 ( 记 作 x ) (记作x) (x) f ( x ) f(x) f(x),满足下面的等式

f ( i ) = f ( j ) ( i ∈ l ) f(i) = f(j) (i \in l) f(i)=f(j)(il

解释:这个等式相当于从 i i i走到 j j j后再前往任意一个被标记的点(但不走回头路)

所以我们可以在 l l l 集合选一个标兵(并查集的树根),用 T a Ta Ta 来表示整个并查集(因为整个并查集的f(x)相等)

参考代码

void UnionSet (int x, int y) {
	int u = FindSet (x), v = FindSet (y);
	if (u == v) return;
	fa[u] = v;
	_min[v] = Min (_min[u], _min[v]);
}

bool dfs (int u, int father) {
	if (check (start, u)) return 1;
	for (int i = head[u]; i; i = e[i].next) {
		int v = e[i].to;
		if (v == father) continue;
		bool flag = dfs (v, u);
		if (flag == 1) {
			vis[u] = 1;
			UnionSet (start, u);
			return 1;
		}
	}
	return 0;
}

②操作二应该做的操作

将并查集记作 s s s

对于每个点的 f ( x ) f(x) f(x) ,我们可以得出

f ( x ) = M i n ( ( f ( j ) , d p ( x , j ) ) ( j ∈ s ) f(x) = Min ((f(j), dp(x, j))(j \in s) f(x)=Min((f(j),dp(x,j))(js)

( d p ( x , j ) 表 示 x 走 到 j 的 路 径 上 的 点 的 最 小 值 ) (dp(x,j)表示x走到j的路径上的点的最小值) (dp(x,j)xj)

d i s ( x , j ) dis(x, j) dis(x,j)时我们可以用一个类似于并查集路径压缩的思想,得出下列转移式

d p ( x , j ) = M i n ( x , d p ( y , j ) ) ( y 是 x 的 子 节 点 ) dp(x, j) = Min (x, dp(y, j)) (y是x的子节点) dp(x,j)=Min(x,dp(y,j))(yx)

同时,我们搜到一个点 j ( j ∈ s ) j(j \in s) j(js) 时,还要保存这个点的编号 (去找 f ( j ) ∈ s f(j) \in s f(j)s)

同样可以用一个类似于并查集路径压缩的思想,得出下列转移式

i n d e x [ x ] = i n d e x [ y ] ( y 是 x 的 子 节 点 ) index[x] = index[y](y是x的子节点) index[x]=index[y](yx)

i n d e x [ i ] 指 向 一 个 在 并 查 集 内 的 一 个 节 点 的 下 标 index[i]指向一个在并查集内的一个节点的下标 index[i]

参考代码

int find_root (int u, int father)  {
	if (check (u, start)) {
		return u;
	}
	for (int i = head[u]; i; i = e[i].next) {
		int v = e[i].to;
		if (v == father) continue;
		int flag = find_root (v, u);
		if (flag == -1) continue;
		index[u] = flag;
		dp[u] = Min (u, dp[v]);
		return flag;
	}
	return -1;
}

参考代码 (综上所述)

#include <cstdio> 
#include <iostream>
using namespace std;

const int MAXN = 1e5 + 5;
const int INF = 0x3f3f3f3f;

int n, q, start = -1;
int fa[MAXN], _min[MAXN], index[MAXN], dp[MAXN];
//dp表示走到并查集的路径中的最小值 
bool vis[MAXN];
//vis[i] == 1表示i在并查集里面 

int len, head[MAXN];
struct edge {
	int to, next;
}e[MAXN * 2];

int Min (int x, int y) { return x < y ? x : y; }

int read () {
	int x = 0, f = 1;
	char tem = getchar ();
	while (tem < '0' || tem > '9') {
		if (tem == '-') f = -1;
		tem = getchar ();
	}
	while (tem >= '0' && tem <= '9') {
		x = (x << 1) + (x << 3) + tem - '0';
		tem = getchar ();
	}
	return x * f;
}

void MakeSet () {
	for (int i = 1; i <= n; i++) {
		fa[i] = i;
		_min[i] = i;
		index[i] = i;
		dp[i] = INF;
	}
}

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

void UnionSet (int x, int y) {
	int u = FindSet (x), v = FindSet (y);
	if (u == v) return;
	fa[u] = v;
	_min[v] = Min (_min[u], _min[v]);
}

bool check (int x, int y) {
	int u = FindSet (x), v = FindSet (y);
	if (u == v) return 1;
	else return 0;
}

bool dfs (int u, int father) {
	if (check (start, u)) return 1;
	for (int i = head[u]; i; i = e[i].next) {
		int v = e[i].to;
		if (v == father) continue;
		bool flag = dfs (v, u);
		if (flag == 1) {
			vis[u] = 1;
			UnionSet (start, u);
			return 1;
		}
	}
	return 0;
}

int find_root (int u, int father)  {
	if (check (u, start)) {
		return u;
	}
	for (int i = head[u]; i; i = e[i].next) {
		int v = e[i].to;
		if (v == father) continue;
		int flag = find_root (v, u);
		if (flag == -1) continue;
		index[u] = flag;
		dp[u] = Min (u, dp[v]);
		return flag;
	}
	return -1;
}

void add (int x, int y) {
	e[++len].to = y;
	e[len].next = head[x];
	head[x] = len;
}

int main () {
	n = read (); q = read ();
	MakeSet ();
	for (int i = 1; i < n; i++) {
		int x, y; x = read (); y = read ();
		add (x, y); add (y, x);
	}
	for (int i = 1; i <= q; i++) {
		int type, x; type = read (); x = read ();
		if (type == 1) {
			if (start == -1) {
				start = x;
				continue;
			}
			dfs (x, -1);
		}
		else {
			if (vis[x] == 0) {//不在并查集内 
				if (dp[x] == INF) {
					find_root (x, -1);
				}
				printf ("%d\n", Min (dp[x], _min[FindSet (index[x])]));
			}
			else {//在并查集内 
				printf ("%d\n", _min[FindSet (x)]);
			}
		}
//		for (int j = 1; j <= n; j++) printf ("root[%d] = %d, _min = %d\n", j, FindSet (j), _min[FindSet (j)]);
	}
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值