【2017 BUAA ACM选拔赛 这是个难题】无向图Tarjan | 边双连通分量 | BFS/DFS | 思维 | 分类讨论 | STL | H

【2017 校选拔赛】 这是个难题

时间限制: 1000 ms 内存限制: 65536 kb
总通过人数: 7 总提交人数: 9

Tags:分类讨论 Tarjan BFS/DFS 边双连通分量

题目描述

给定 n n n 个点和 m m m 条无向边的连通图,问能否在其中找到一条包含五个不同点的链。



输入

T T T 组。第一行输入组数 T T T

对于每组数据,包含若干行,第一行两个正整数 n , m n,m n,m,代表点数边数。

接下来 m m m 行描述无向连通图,每行两个正整数 u , v u,v u,v,表示一条连接 u u u v v v 号点的无向边。

数据保证不含重边,并且 1 ≤ u , v ≤ n 1 \le u, v \le n 1u,vn

其中, 1 ≤ T ≤ 15 , 1 ≤ n , m ≤ 1 0 3 1≤T≤15,1≤n,m≤10^3 1T151n,m103



输出

每组输入对应输出一行,有则输出 y e s yes yes,否则输出 n o no no


输入样例

2
5 4
1 2
2 3
3 4
4 5
4 5
1 2
2 3
3 4
4 1
4 2

输出样例

yes
no



分析

曾做过一道有关无向图桥的题,于是想到本题可以用无向图的 T a r j a n Tarjan Tarjan 来缩点,然后把整个连通图变成由一些 边双连通分量 构成的无向树。

然后直接求树的直径?不可。因为这棵树的每个点都是一个边双连通分量,所以树的直径后不一定是原图中的最长链。而且由于连通分量内部连接可能很复杂,所以要正确求出通过一个连通分量内部最多经过的结点数也不易。

但是别忘了一件事:询问长度刚好是 5 5 5 。这个数很小,所以我们完全可以 分类讨论 来解题:

  • 若存在至少含 5 5 5 个顶点的边双连通分量:输出 yes
  • 否则:
    • 若只有一个边双连通分量:输出 no
    • 否则:
      • 若存在含 4 4 4 个顶点的边双连通分量:输出 yes
      • 若每个连通分量都是一个顶点,则求树直径。若直径 ≥ 5 \ge 5 5输出 yes,否则 输出 no
      • 否则必存在含 3 3 3 个顶点的边双连通分量:(本题无重边,不可能存在含 2 2 2 顶点的边双连通分量)
        • 若至少存在 2 2 2 个这样的分量:输出 yes
        • 否则只有 1 1 1 个这样的分量:
          • 如果这个三角形至少有两个顶点都有不属于三角形自身的邻接点:输出 yes
          • 否则:输出 no

好了,这就算完了。是不是很简单呢 一定要细心讨论,不重不漏。

时间复杂度:
  • 酷酷的 T a r j a n Tarjan Tarjan O ( V + E ) O(V+E) O(V+E) 的。
  • 至多两次 B F S BFS BFS,也是 O ( V + E ) O(V+E) O(V+E) 的。
  • 没有 B F S BFS BFS 的话,也有可能会线性扫描顶点特判了, O ( V ) O(V) O(V) 的。
  • 总时间复杂度 O ( V + E ) O(V+E) O(V+E)



AC代码

#include <stdio.h>
#include <string.h>

#define sc(x) {register char _c=getchar(),_v=1;for(x=0;_c<48||_c>57;_c=getchar())if(_c==45)_v=-1;for(;_c>=48&&_c<=57;x=(x<<1)+(x<<3)+_c-48,_c=getchar());x*=_v;}
constexpr int MV(1e3+7);
constexpr int ME(2e3+7);
struct Edge
{
	int dest;
	int next;
} edge[ME];
int head[MV];

int V1, V2, E1;
int max_v;

int dnt, dfn[MV], low[MV];
int stack[MV], top;
int belong[MV];
int ring[MV];

bool vis[MV];
int dep[MV];
int queue[MV];

void Tarjan(const int u, const int fa)
{
	if (max_v >= 5)
		return;

	stack[top++] = u;
	dfn[u] = low[u] = ++dnt;

	for (int i=head[u]; i; i=edge[i].next)
	{
		const int v = edge[i].dest;
		if (v != fa)
		{
			if (!dfn[v])
			{
				Tarjan(v, u);
				if (low[u] > low[v])
					low[u] = low[v];
			}
			else
			{
				if (low[u] > dfn[v])
					low[u] = dfn[v];
			}
		}
	}
	if (dfn[u] == low[u])
	{
		++V2;
		int now = -1;
		while (now != u)
		{
			now = stack[--top];
			belong[now] = V2;
			++ring[V2];
		}
		if (max_v < ring[V2])
			max_v = ring[V2];
	}
}


void read_edge(void)
{
	memset(head+1, 0, sizeof(*head)*V1);
	for (int e=1, EE=2*E1; e<=EE; ++e)
	{
		int u, v;
		sc(u)sc(v)
		edge[e].next = head[u];
		edge[e].dest = v;
		head[u] = e;

		edge[++e].next = head[v];
		edge[e].dest = u;
		head[v] = e;
	}
}

int bfs(const int SRC)
{
	int f = -1 , h = 0, t = 0;
	memset(vis+1, 0, sizeof(*vis)*V1);
	dep[SRC] = 1;

	queue[t++] = SRC, vis[SRC] = true;
	while (h != t)
	{
		const int u = queue[h++];
		for (int i=head[u]; i; i=edge[i].next)
		{
			const int v = edge[i].dest;
			if (!vis[v])
			{
				queue[t++] = v, vis[v] = true;
				dep[v] = dep[u] + 1;
				if (max_v < dep[v])
					max_v = dep[f = v];
				if (max_v >= 5)
					return f;
			}
		}
	}

	return f;
}


int main()
{
	int T;
	sc(T)
	while (T--)
	{
		sc(V1)sc(E1)
		dnt = top = V2 = 0;
		max_v = 1;
		memset(dfn+1, 0, sizeof(*dfn)*V1);
		memset(low+1, 0, sizeof(*low)*V1);
		memset(ring+1, 0, sizeof(*ring)*V1);

		read_edge();
		Tarjan(1, 1);	// 连通图,一次 Tarjan 即可求出所有边双连通分量
		if (max_v < 5)
		{
			if (V2 == 1)
				puts("no");
			else
			{
				if (max_v == 4)
					puts("yes");
				else if (max_v == 3)
				{
					int t_cnt = 0, t_index = 0;
					for (int v=1; v<=V2 && t_cnt<2; ++v)
						if (ring[v] == 3)
							++t_cnt, t_index = v;
					if (t_cnt == 2)
						puts("yes");
					else
					{
						if (V2 == 2)
							puts("no");
						else
						{
							int out = 0;
							for (int u=1; u<=V1; ++u)
							{
								if (belong[u] == t_index)
								{
									for (int i=head[u]; i; i=edge[i].next)
										if (belong[edge[i].dest] != t_index)
										{
											++out;
											break;
										}
								}
							}
							puts(out >= 2 ? "yes" : "no");
						}
					}
				}
				else // 本题max_v不会为2,因不含重边。现在只会为1,即整个图是一棵无向树。
				{
					int f1 = bfs(1);
					if (max_v >= 5)
						puts("yes");
					else
					{
						bfs(f1);
						puts(max_v >= 5 ? "yes" : "no");
					}
				}
			}
		}
		else puts("yes");
	}
}





嘻,你居然看到这了?那我再偷偷再放一个瞎 D F S DFS DFS + 暴力 s t d : : n t h _ e l e m e n t std::nth\_element std::nth_element 的代码 :

#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <vector>

#define sc(x) {register char _c=getchar(),_v=1;for(x=0;_c<48||_c>57;_c=getchar())if(_c==45)_v=-1;for(;_c>=48&&_c<=57;x=(x<<1)+(x<<3)+_c-48,_c=getchar());x*=_v;}

constexpr int MV(1e3+7);

struct Edge
{
	int dest;
	int next;
} edge[2 * MV];	// 无向图,二倍
int head[MV];
bool vis[MV];

std::vector<int> len[MV];

void read_edge(const int V, const int E)
{
	memset(head+1, 0, sizeof(*head) * V);
	for (int e=1, EE=2*E; e<=EE; ++e)
	{
		int u, v;
		sc(u)sc(v)
		edge[e].next = head[u];
		edge[e].dest = v;
		head[u] = e;

		edge[++e].next = head[v];
		edge[e].dest = u;
		head[v] = e;
	}
}

int dfs(int u) // 求每个点出发的最长段
{
	int max = 0;
	for (int i=head[u]; i; i=edge[i].next)
	{
		const int v = edge[i].dest;
		if (!vis[v])
		{
			vis[v] = true;
			int len_v = dfs(v);
			if (max < len_v)
				max = len_v;
			len[u].emplace_back(len_v);
		}
	}
	return max + 1; // +1是加上自己
}

void dfs_all(const int V) // 都dfs
{
	memset(vis+1, false, sizeof(*vis) * V);
	for (int v=1; v<=V; ++v)
		len[v].clear();
	for (int v=1; v<=V; ++v)
		if (!vis[v])
			vis[v] = true, dfs(v);
}

int get_max_len(const int V) // 遍历求最大长
{
	int max_len = 1;
	for (int v=1; v<=V; ++v)
	{
		int size = len[v].size();
		if (size >= 2)
		{
			std::nth_element(len[v].begin(), len[v].end()-2, len[v].end()); // O(n)内求两个最大值
			auto ED = len[v].end();
			int tp = ED[-2] + ED[-1] + 1; // +1是加上自己
			if (tp > max_len)
				max_len = tp;
		}
		else if (size)
		{
			if (len[v].front()+1 > max_len)
				max_len = len[v].front()+1; // +1是加上自己
		}
	}
	return max_len;
}

int main()
{
	int T;
	sc(T)
	while (T--)
	{
		int V, E;
		sc(V)sc(E)

		read_edge(V, E);
		dfs_all(V);

		int max_len = get_max_len(V);

		puts(max_len >= 5 ? "yes" : "no");
	}
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值