洛谷 P2783 有机化学之神偶尔会做作弊(Tarjan边双连通分量,LCA,并查集)

题目链接

洛谷 P2783 有机化学之神偶尔会做作弊

思路

在一张无向连通图图上,将所有的环全都缩成一个点,我们可以使用Tarjan边双连通分量进行割边缩点。

缩点之后的图一定是一棵树。

在这棵树上,我们想要快速求出 a a a和 b b b两个点之间的节点个数,可以先预处理出这棵树的LCA。

利用每一个节点的深度进行快速求解,公式为:

d e p t h [ a ] + d e p t h [ b ] − d e p t h [ l c a ] ∗ 2 + 1 depth[a] + depth[b] - depth[lca]*2 + 1 depth[a]+depth[b]−depth[lca]∗2+1。

代码

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e4 + 5, M = 1e5 + 5;
const int inf = 0x3f3f3f3f3f3f3f3f;

int n, m, tot;
int u[M], v[M];
vector<pair<int, int>>edge;

int h[N], e[M], ne[M], idx;
int dfn[N], low[N], tim;
int id[N], bcc_cnt;
stack<int> st;
bool is_bridge[M];
void add(int a, int b)
{
	e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
void tarjan(int u, int fu)
{
	dfn[u] = low[u] = ++tim;
	st.push(u);
	for (int i = h[u]; i != -1; i = ne[i])
	{
		int j = e[i];
		if (!dfn[j])
		{
			tarjan(j, i);
			low[u] = min(low[u], low[j]);
			if (dfn[u] < low[j])
			{
				is_bridge[i] = is_bridge[i ^ 1] = true;
			}
		}
		else if (i != (fu ^ 1))
		{
			low[u] = min(low[u], dfn[j]);
		}
	}
	if (dfn[u] == low[u])
	{
		++bcc_cnt;
		int k;
		do
		{
			k = st.top();
			st.pop();
			id[k] = bcc_cnt;
		} while (k != u);
	}
}

struct DSU {
	std::vector<int> f, siz;

	DSU() {}
	DSU(int n) {
		init(n);
	}

	void init(int n) {
		f.resize(n);
		std::iota(f.begin(), f.end(), 0);
		siz.assign(n, 1);
	}

	int find(int x) {
		while (x != f[x]) {
			x = f[x] = f[f[x]];
		}
		return x;
	}

	bool same(int x, int y) {
		return find(x) == find(y);
	}

	bool merge(int x, int y) {
		x = find(x);
		y = find(y);
		if (x == y) {
			return false;
		}
		siz[x] += siz[y];
		f[y] = x;
		return true;
	}

	int size(int x) {
		return siz[find(x)];
	}
};

struct LCA
{
	vector<vector<int>>mp;
	vector<int>depth;
	vector<vector<int>>fa;

	LCA() {}
	LCA(int n) {init(n);}

	void init(int n)
	{
		mp.resize(n + 1);
		depth.resize(n + 1);
		fa.resize(n + 1, vector<int>(20));
	}

	void add_edge(int a, int b)
	{
		//建双向边
		mp[a].push_back(b);
		mp[b].push_back(a);
	}

	void bfs(int root)
	{
		fill(depth.begin(), depth.end(), inf);
		depth[0] = 0, depth[root] = 1;
		queue<int>q;
		q.push(root);
		while (q.size())
		{
			int u = q.front();
			q.pop();
			for (int i = 0; i < mp[u].size(); i++)
			{
				int j = mp[u][i];
				if (depth[j] > depth[u] + 1)
				{
					depth[j] = depth[u] + 1;
					q.push(j);
					fa[j][0] = u;
					for (int k = 1; k <= 19; k++)
					{
						fa[j][k] = fa[fa[j][k - 1]][k - 1];
					}
				}
			}
		}
	}

	int lca(int a, int b)
	{
		if (depth[a] < depth[b]) swap(a, b);
		for (int k = 19; k >= 0; k -- )
			if (depth[fa[a][k]] >= depth[b])
				a = fa[a][k];
		if (a == b) return a;
		for (int k = 19; k >= 0; k -- )
			if (fa[a][k] != fa[b][k])
			{
				a = fa[a][k];
				b = fa[b][k];
			}
		return fa[a][0];
	}
};

void solve()
{
	cin >> n >> m;
	memset(h, -1, sizeof h);
	for (int i = 1; i <= m; i++)
	{
		cin >> u[i] >> v[i];
		if (u[i] == v[i]) continue;
		edge.push_back({min(u[i], v[i]), max(u[i], v[i])});
	}
	sort(edge.begin(), edge.end());
	int high = unique(edge.begin(), edge.end()) - edge.begin();
	for (int i = 0; i < high; i++)
	{
		add(edge[i].first, edge[i].second);
		add(edge[i].second, edge[i].first);
	}

	for (int i = 1; i <= n; i++)
	{
		if (!dfn[i])
			tarjan(i, 0);
	}

	DSU dsu(bcc_cnt + 1);
	LCA tree(bcc_cnt);//tarjan边双连通分量之后的图一定是一棵树
	for (int i = 0; i < high; i++)
	{
		int bcc_u = id[edge[i].first], bcc_v = id[edge[i].second];
		if (bcc_u != bcc_v && !dsu.same(bcc_u, bcc_v))
		{
			tree.add_edge(bcc_u, bcc_v);
			dsu.merge(bcc_u, bcc_v);
		}
	}
	tree.bfs(1);

	cin >> tot;
	while (tot--)
	{
		int a, b;
		cin >> a >> b;
		a = id[a];
		b = id[b];

		int zu = tree.lca(a, b);

		int ans = tree.depth[a] + tree.depth[b] - tree.depth[zu] * 2 + 1;

		vector<int>bit;
		while (ans)
		{
			bit.push_back(ans % 2);
			ans /= 2;
		}
		reverse(bit.begin(), bit.end());
		for (int val : bit)
		{
			cout << val;
		}
		cout << endl;
	}

}
signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(0), cout.tie(0);
	int test = 1;
	// cin >> test;
	for (int i = 1; i <= test; i++)
	{
		solve();
	}
	return 0;
}
相关推荐
张三不嚣张3 分钟前
PPO(近端策略优化)算法基本原理
人工智能·算法·强化学习·游戏策划
计科土狗3 分钟前
巧妙算法之位运算的应用
c++
游是水里的游5 分钟前
【算法day25】贪心:重叠区间2
算法
云青山水林18 分钟前
比较各种排序方法的实现思想、优缺点和适用场合
数据结构·算法·排序算法
缸缸---22 分钟前
TOP K问题:利用堆排序找出数组中最小的k个数
数据结构·算法
初阳78532 分钟前
12. 日常算法
算法
Abelard_36 分钟前
LeetCode--排序算法(堆排序、归并排序、快速排序)
算法·leetcode·排序算法
姚先生9736 分钟前
LeetCode 349. 两个数组的交集 (C++实现)
c++·算法·leetcode
m0_7482404439 分钟前
使用 DBSCAN(基于密度的聚类算法) 对二维数据进行聚类分析
算法·数据挖掘·聚类
m0_5195231044 分钟前
算法练习——模拟题
数据结构·c++·学习·算法