E - SPF(无向图连通分量求割点)

题目链接
Consider the two networks shown below. Assuming that data moves around these networks only between directly connected nodes on a peer-to-peer basis, a failure of a single node, 3, in the network on the left would prevent some of the still available nodes from communicating with each other. Nodes 1 and 2 could still communicate with each other as could nodes 4 and 5, but communication between any other pairs of nodes would no longer be possible.

Node 3 is therefore a Single Point of Failure (SPF) for this network. Strictly, an SPF will be defined as any node that, if unavailable, would prevent at least one pair of available nodes from being able to communicate on what was previously a fully connected network. Note that the network on the right has no such node; there is no SPF in the network. At least two machines must fail before there are any pairs of available nodes which cannot communicate.
在这里插入图片描述
Input
The input will contain the description of several networks. A network description will consist of pairs of integers, one pair per line, that identify connected nodes. Ordering of the pairs is irrelevant; 1 2 and 2 1 specify the same connection. All node numbers will range from 1 to 1000. A line containing a single zero ends the list of connected nodes. An empty network description flags the end of the input. Blank lines in the input file should be ignored.
Output
For each network in the input, you will output its number in the file, followed by a list of any SPF nodes that exist.

The first network in the file should be identified as “Network #1”, the second as “Network #2”, etc. For each SPF node, output a line, formatted as shown in the examples below, that identifies the node and the number of fully connected subnets that remain when that node fails. If the network has no SPF nodes, simply output the text “No SPF nodes” instead of a list of SPF nodes.
Sample Input
1 2
5 4
3 1
3 2
3 4
3 5
0

1 2
2 3
3 4
4 5
5 1
0

1 2
2 3
3 4
4 6
6 3
2 5
5 1
0

0
Sample Output
Network #1
SPF node 3 leaves 2 subnets

Network #2
No SPF nodes

Network #3
SPF node 2 leaves 2 subnets
SPF node 3 leaves 2 subnets

题目大意
给一个网络,节点与节点相连,让你求去掉某些节点能不能使得整个网络不联通。
解题思路
就是求关节点。(去掉使得图不联通的点)

1.直接DFS,枚举删除每个点,看需要dfs多少次才能把所有点遍历完,注意点标号不连续。居然也是0ms。时间复杂度O(n^3),邻接表其实也优化不了稠密图。

2.Tarjan算法,其实就是记忆化搜索的思路。

原理:对于一个图,对它进行深度优先搜索之后,会产生一颗深度优先搜索树(其实就是搜索的路径)。

节点是关节点的充要条件是:

1.它是树的根节点并有2个以上子女。(很明显如果这个点没了,树左右2遍就断开了,记得是搜索树的子女,不是该节点有几个邻接点,邻接点不一定是搜索树的子女)

2.它不是根节点,但是以它出发向下的路径,没有节点能够返回在当前节点之前的点。

第一点好判断,第二点怎么判断呢?

我们引入一个dfn[]数组记录搜索的次序,祖先一定大于子女,

然后再引入一个low[]数组,(作用为记录其可以直接或间接访问的最上层的顶点)使其为本身的dfn[]、能通过当前节点返回其祖先或其祖先之上的节点的dfn[]和他子女的low[]的最小值,这样就能记录整条路径有没有回路了。

于是条件2转化为u不是根节点,并且存在子女v,使得low[v]>=dfn[u],只要看子女就行,因为low[]记录了整条路径的最小值。并且有多少个子女满足就分成多少块。

整个过程在DFS中进行,利用回溯进行low值的确定实在是太精妙了。时间复杂度网上很多都说O(n+m),顶点数+边数,因为他们认为扫所有的点,扫所有的边。我觉得这忽略了从一个点出发,扫其余点时候所需要的判断,稠密图邻接表也没用,不过这题数据很弱。

这题我用的tarjan其实是假设在搜索树的每个顶点都有一条回边回到他的直接祖先。(都有重边)不然的话我就要标记每条边,看看是否走过,因为不管是邻接表还是邻接矩阵,都无法确定是通过原来这条边访问节点的呢,还是有一条成环的边。就是我假设如下图:

这并不影响算法的正确性,因为2个点本来就相连,我加边或者不加边,一个点失去之后,该断的还是会断。但是如果求割边就不一样的。

网上许多代码都是这样的,只要是没标记边的,不信你手动模拟tarjan算法,然后打印每个low[]值看看。

1.DFS代码


#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
#include <vector>
 
using namespace std;
 
const int MAX_SIZE = 1002,
          INF = 0x3f3f3f3f;
 
struct elem
{
    int id, cnt;
};
vector <int> G[MAX_SIZE];
vector <elem> SPF;
bool visited[MAX_SIZE], isNode[MAX_SIZE];
int nodeNum = 0, del;
 
void addEdge(int u, int v)
{
    G[u].push_back(v);
    G[v].push_back(u);
    nodeNum = max(nodeNum, max(u, v));
    isNode[u] = isNode[v] = true;
}
 
void init()
{
    int i;
    for(i = 1; i <= 1000; i++)
        G[i].clear();
    SPF.clear();
    memset(isNode, 0, sizeof(isNode));
}
 
void dfs(int u)
{
    visited[u] = true;
    int i, v;
    for(i = 0; i < G[u].size(); i++){
        v = G[u][i];
        if(v != del && !visited[v]){
            dfs(v);
        }
    }
}
 
int main()
{
    int u, v, i, j, kase = 1, cnt;
 
    while(1){
        init();
        scanf("%d", &u);
        if(0 == u)
            break;
        scanf("%d", &v);
        addEdge(u, v);
        while(1){
            scanf("%d", &u);
            if(0 == u)
                break;
            scanf("%d", &v);
            addEdge(u, v);
        }
 
        for(i = 1; i <= nodeNum; i++){
            if(!isNode[i])
                continue;
            del = i;
            memset(visited, 0, sizeof(visited));
            cnt = 0;
            for(j = 1; j <= nodeNum; j++){
                if(j != del && !visited[j] && isNode[j]){
                    dfs(j);
                    cnt++;
                }
            }
            if(cnt > 1){
                elem tmp;
                tmp.id = del, tmp.cnt = cnt;
                SPF.push_back(tmp);
            }
        }
 
        printf("Network #%d\n", kase++);
        if(0 == SPF.size())
            printf("  No SPF nodes\n");
        else
            for(i = 0; i < SPF.size(); i++)
                printf("  SPF node %d leaves %d subnets\n", SPF[i].id, SPF[i].cnt);
 
        printf("\n");
    }
}

2.Tarjan算法


#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
#include <stdlib.h>
#include <math.h>
#include <ctype.h>
#include <queue>
#include <map>
#include <set>
#include <algorithm>
 
using namespace std;
int head[1100], cnt, index1, flag;
int low[1100], dfn[1100], ge[1100], vis[1100];
struct node
{
    int u, v, next;
}edge[100000];
void add(int u, int v)
{
    edge[cnt].v=v;
    edge[cnt].next=head[u];
    head[u]=cnt++;
}
void tarjan(int u, int fa)
{
    low[u]=dfn[u]=++index1;
    vis[u]=1;
    int son=0, i;
    for(i=head[u];i!=-1;i=edge[i].next)
    {
        int v=edge[i].v;
        son++;
        if(!vis[v])
        {
            tarjan(v,u);
            low[u]=min(low[u],low[v]);
            if(u==1&&son>1||u!=1&&dfn[u]<=low[v])
            {
                ge[u]++;
                flag=1;
            }
        }
        else if(v!=fa)
            low[u]=min(low[u],dfn[v]);
    }
}
void init()
{
    memset(head,-1,sizeof(head));
    cnt=0;
    memset(dfn,0,sizeof(dfn));
    memset(vis,0,sizeof(vis));
    memset(ge,0,sizeof(ge));
    index1=0;
    flag=0;
}
int main()
{
    int u, v, i, j, num=0, n;
    while(scanf("%d",&u)!=EOF&&u)
    {
        num++;
        init();
        scanf("%d",&v);
        add(u,v);
        add(v,u);
        n=max(u,v);
        while(scanf("%d",&u)!=EOF&&u)
        {
            scanf("%d",&v);
            add(u,v);
            add(v,u);
            n=max(n,max(u,v));
        }
        tarjan(1,-1);
        printf("Network #%d\n",num);
        if(!flag)
        {
            printf("  No SPF nodes\n\n");
            continue ;
        }
        for(i=1;i<=n;i++)
        {
            if(ge[i])
            {
                printf("  SPF node %d leaves %d subnets\n",i,ge[i]+1);
            }
        }
        printf("\n");
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值