【2017 校选拔赛】 这是个难题
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 1≤u,v≤n。
其中, 1 ≤ T ≤ 15 , 1 ≤ n , m ≤ 1 0 3 1≤T≤15,1≤n,m≤10^3 1≤T≤15,1≤n,m≤103。
输出
每组输入对应输出一行,有则输出
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");
}
}