Loading

Waiting for Hack

无向图最小环(边权为1)

现在给一张 \(n\) 个点 \(m\) 条边的无向图,求图中最小环的长度。

很久以前就想过类似的问题,但是一直犯懒没打。
无复杂度保证,只是本彩笔卡不掉而已

只考虑没有重边自环的情况。

我们重复进行以下操作:

  1. 拓扑排序。将度为 \(1\) 的点删掉,然后更新其他节点的度,直到所有的点的度都 $ > 1$ 为止。

  2. 选择一个 度最大 的点,以这个点为起点跑 BFS,找最小环,更新答案。然后把这个节点删掉。

如果所有点都被删掉了就结束。

正确性显然,但是需要注意几个细节:

  1. 使用桶来维护度。由于度单调不增,直接开桶从大往小扫就行。
    至于为什么不能每次扫所有点找度最大,可以往下看。

  2. BFS。若以 \(a\) 为起点找环,则可以将 \(a\) 的所有相邻节点染上不同的颜色。
    如果在 BFS 更新最短路的时候发现两个不同颜色的点更新了同一个点则发现了最小环。
    当然,如果发现以 \(a\) 为起点无法找到比之前更小的环,那就当做找到了,没必要接着找了。
    在找到最小环之后应该立即退出以确保复杂度

  3. 找环前不要全局 memset。此算法的复杂度基于每次 BFS 不会跑满,所以跑了多少就清空多少。
    一种比较方便的实现是用数组模拟队列,最后重新扫一遍队列即可。

  4. 关于删边。显然我们在删掉一个点之后需要把与其相邻的边也删掉。
    可以使用前向星存图,延迟删除,然后给删了的点打标记。如果访问到了再把这条边删了。

以及,如果图是随机生成的,大概率答案为 \(3\)。当现有的答案已经为 \(3\) 时我们可以直接退出。
不会影响复杂度,但是随机数据下确实会优化不少。

简洁的代码实现就放下面了,心情好的话就 \(hack\) 一下吧。

A Big Tuo of Shit
#include <vector>
#include <stdio.h>
#define sz 100005
using namespace std;
const int inf = 0x3f3f3f3f;
struct site
{
	int ed, next;
};
site dld[sz << 1];
int n, m, now, mx, toans;
bool del[sz];
int fsl[sz], ds[sz], dep[sz], col[sz], que[sz];
vector<int> ts[sz];
int read();
void net(int, int);
void del_p(int);
int sol();
void dfs();
int geta();
int bfs(int);
int main()
{
	int x, y;
	n = read(); m = read();
	for (int i = 1; i <= m; ++i)
	{
		x = read(); y = read();
		net(x, y); net(y, x);
	}
	for (int i = 1; i <= n; ++i)
	{
		mx = max(mx, ds[i]);
		ts[ds[i]].emplace_back(i);
	}
	printf ("%d\n", sol());
	return 0;
}

int read()
{
	int x = 0;
	char c = getchar();
	while (c < '0') c = getchar();
	do {
		x = x * 10 + (c & 15);
		c = getchar();
	}while (c >= '0');
	return x;
}

void net(int a, int b)
{
	++ds[b];
	dld[++now].ed = b;
	dld[now].next = fsl[a];
	fsl[a] = now;
}

void del_p(int x)
{
	ds[x] = 0;
	del[x] = true;
	for (int i = fsl[x]; i; i = dld[i].next)
		if (!del[dld[i].ed])
		{
			--ds[dld[i].ed];
			ts[ds[dld[i].ed]].emplace_back(dld[i].ed);
		}
}

int sol()
{
	toans = inf;
	int x;
	while (1)
	{
		dfs();
		x = geta();
		if (x == -1)
			break;
		toans = min(toans, bfs(x));
		if (toans <= 3)
			break;
		del_p(x);
	}
	return toans == inf ? -1 : toans;
}

void dfs()
{
	int x;
	while (ts[1].size() + ts[0].size())
		for (int i = 0; i < 2; ++i)
			while (ts[i].size())
			{
				x = ts[i].back();
				ts[i].pop_back();
				if (!del[x])
					del_p(x);
			}
}

int geta()
{
	int x;
	while (mx)
	{
		while (ts[mx].size())
		{
			x = ts[mx].back();
			ts[mx].pop_back();
			if (ds[x] == mx)
				return x;
		}
		--mx;
	}
	return -1;
}

int bfs(int a)
{
	int lf = 1, rt = 0, x, ans = inf, mn;
	while (del[dld[fsl[a]].ed])
		fsl[a] = dld[fsl[a]].next;
	for (int i = fsl[a], last = 0; i; last = i, i = dld[i].next)
		if (del[dld[i].ed])
		{
			dld[last].next = dld[i].next;
			i = last;
		}else
		{
			que[++rt] = dld[i].ed;
			dep[dld[i].ed] = 1;
			col[dld[i].ed] = rt;
		}
	while (rt >= lf)
	{
		x = que[lf++];
		if ((dep[x] << 1) >= toans || (ans != inf && dep[x] > mn))
			break;
		while (fsl[x] && del[dld[fsl[x]].ed])
			fsl[x] = dld[fsl[x]].next;
		for (int i = fsl[x], last = 0; i; last = i, i = dld[i].next)
			if (dld[i].ed != a)
			{
				if (del[dld[i].ed])
				{
					dld[last].next = dld[i].next;
					i = last;
				}else if (!dep[dld[i].ed])
				{
					dep[dld[i].ed] = dep[x] + 1;
					que[++rt] = dld[i].ed;
					col[dld[i].ed] = col[x];
				}else if (col[dld[i].ed] != col[x])
				{
					ans = min(ans, dep[x] + dep[dld[i].ed] + 1);
					mn = dep[x];
					break;
				}
			}
	}
	for (int i = 1; i <= rt; ++i)
		dep[que[i]] = 0;
	for (int i = 1; i <= rt; ++i)
		col[que[i]] = 0;
	return ans;
}

\(Updated:\)
\(Lyin\) 提供的一种 \(Hack\) 方法,可以在稀疏图下卡到 \(O(n \sqrt n)\)
数据生成器放在这里吧。

Data Creator
#include <math.h>
#include <stdio.h>
int n = 100000, dt = sqrt(n), m = n + n / dt;
int main()
{
	freopen ("a.in", "w", stdout);
	printf ("%d %d\n", n, m);
	for (int i = 1; i < n; ++i)
		printf ("%d %d\n", i, i + 1);
	for (int j = 1, k = n; k <= m; ++k, j += dt - 1)
		printf ("%d %d\n", j, j + dt - 1);
	return 0;
}

posted @ 2023-03-01 15:42  hzoi_Sakura  阅读(78)  评论(4编辑  收藏  举报