洛谷 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

代码

#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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值