POJ1523_SPF_求割点与分块_tarjan算法

【PS:昨晚跟主任讨论的时候才发现自己的代码错了= =没想到错误的代码也能A,只能说明这道题的数据太弱= =为了警示自己,把错误的代码也留下来,附在最后面】


题意:

给一个无向图,求其中的割点,并求当此割点删掉后图中形成多少个连通分量

题解:

用tarjan求割点:伪代码

求割点的方法就用上面的,d[u]和low[u]的含义是:

d[u],dfs到结点u时的时间

low[u],由u在搜索树中的子节点通过非父子边可以追溯到的最早的结点开始的时间

时间:每调用一次dfs函数,时间增加1

原理我就不啰嗦了,看看tarjan算法的证明和实现过程就能大致明白。

关键这里还要求分块,看到网上有解题报告是又dfs一遍根据low数组来求,觉得比较麻烦,想想其实可以不用再dfs了,

在搜索过程中如果当前结点是割点,那么在搜索树中,每一个子节点就是一个连通分量,所以可以通过记录子节点的个数来求分块,非根节点的割点求分块,还需要判断是否父亲节点的一支也是一个连通分量,这时就需要看父亲节点的low值,如果跟所有的孙子结点的low值都不同,那么父亲节点也是一个连通分量


总之这次题编的磕磕绊绊,为了实现上面的思路,过程中又加了非常多的变量,导致我的程序非常的复杂,也不易读,计划等下再改一次程序,用更精炼的方法再做一次。

【PS:这题的输入输出让人非常蛋疼= =】

原题:

SPF
Time Limit: 1000MS
Memory Limit: 10000K
Total Submissions: 4638
Accepted: 2138

Description

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

Run IDUserProblemResultMemoryTimeLanguageCode LengthSubmit Time
11805500chengtbf1523Accepted212K0MSC++2297B2013-07-18 10:07:45
正确代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#define N 1003
using namespace std;

int d[N],low[N];//分别记录每个节点的发现时间和所能到达的最早的结点的时间
int dfs_time;
int father[N];
int root;
int visited[N];//记录是否被访问过
int subsets[N];//记录被分成的子节点的个数
bool flag_cut_node[N];//标记是否为割点
vector<int>f[N];//建树专用
int case_num=1;
int find_min(int a,int b)
{
	return a<b?a:b;
}

void tarjan(int u)
{
	visited[u]=1;
	d[u]=low[u]=++dfs_time;
	int i,child;
	for ( i = 0; i< f[u].size(); i++)
	{
		child=f[u][i];
		if (!visited[child])
		{
			father[child]=u;//这里没有判断是否等于父亲,是因为如果是父亲,一定在发现结点u之前就已经被发现了,不可能进入
			tarjan(child);
			low[u]=find_min(low[u],low[child]);//更新此结点
			if (low[child]>=d[u])//表示子节点没有到达u结点的祖先的路径,那么这个子树就是一个连通分支
			{
				subsets[u]++;
			}
		}
		else if (child!=father[u])
		{
			low[u]=find_min(low[u],d[child]);//更新u结点
		}
	}
}

void count()
{
	int i,dad;
	int flag=1;
	for ( i = 1; i <=1000 ; i++)
	{
		dad=father[i];
		if (i==root)//是根节点就看子树是否大于1
		{
			if(subsets[i]>1)
				flag_cut_node[i]=true;
		}
		else if( dad!=root && dad!=-1 &&d[dad]<=low[i])//非根节点需要找自己的儿子有没有满足d[u]<=low[child]
		{
			flag_cut_node[dad]=true;//这个方法非常的优化,因为只需扫一遍就可以求出所有割点,不需要再深搜了
		}
	}
	printf("Network #%d\n",case_num++);
	for ( i = 1; i <=1000 ; i++)
	{
		if (flag_cut_node[i])
		{
			if (i!=root)
			{
				subsets[i]++;//还需要加上父亲结点的那一支
			}
			printf("  SPF node %d leaves %d subnets\n",i,subsets[i]);
			flag=0;//判断是否有割点
		}
	}
	if (flag)
	{
			printf("  No SPF nodes\n");
	}
	printf("\n");
}

int main()
{
	int a,b,i,flag=1;
	while (1)
	{
		for ( i = 1; i <=1000 ; i++)
		{
			f[i].clear();
		}
		memset(subsets,0,sizeof(subsets));
		memset(d,-1,sizeof(d));
		memset(low,-1,sizeof(low));
		memset(visited,0,sizeof(visited));
		memset(father,-1,sizeof(father));
		memset(flag_cut_node,0,sizeof(flag_cut_node));
		dfs_time=0;
		for(i=1;;i++)//输入
		{
			scanf("%d",&a);
			if (a==0)
			{
				if (i==1)
					flag=0;
				break;
			}
			scanf("%d",&b);
			f[a].push_back(b);//双向存树
			f[b].push_back(a);
		}
		if (!flag)break;
		for ( i = 1; i <=1000 ; i++)
		{
			if (f[i].size()!=0)//找到第一个在此连通图中的结点,即为根
			{
				root=i;
				tarjan(root);
				count();
				break;
			}
		}
	}

	return 0;
}


【错误】代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#define N 1002
using namespace std;

struct NODE
{
	int node_num;
	int subnet;
}node[N];//记录割点的编号和所划分的块数


int dfs_time;//此变量用来存发现时间
int node_amount;//存割点数目
int child_num[N];//存dfs树中当前结点u的孩子结点个数
int relative_num[N];//表示跟他相连的结点个数,跟上面一个有区别
int d[N],low[N];//分别为发现时间,其子节点中能到达的最小时间
int root;//根节点
int visited[N];//判断是否访问过
int father[N];
bool whether_cut[N];
vector<int>f[N];
vector<int>find_real_child[N];//存dfs中真正的子节点
int find_min(int a,int b)
{
	if (a<b)return a;
	else return b;
}
void tarjan_dfs(int u)
{
	visited[u]=1;
	d[u]=low[u]=++dfs_time;
	int i;int child;
	for ( i = 0; i < relative_num[u]; i++)
	{
		child=f[u][i];
		
		if (!visited[child])
		{
			if (child!=father[u])
			{
				father[child]=u;
			}
			tarjan_dfs(child);
			low[u]=find_min(low[u],low[child]);
			child_num[u]++;//此时,child是u在搜索树中的子节点
			find_real_child[u].push_back(child);
		}
		else if(child!=father[u])
		{
			low[u]=find_min(low[u],d[child]);
		}
	}

}
void Count()
{
	int i,child,dad,j;
	int flag;
	for ( i = 1; i <=1000; i++)
	{
		dad=father[i];
		if (dad!=root && dad!=-1 && d[dad]<=low[i])
		{
			whether_cut[dad]=true;
		}
	}
	for ( i = 1; i <= 1000; i++)
	{
		if (whether_cut[i])
		{
			flag=1;
			node[node_amount].node_num=i;
			node[node_amount].subnet=child_num[i];
			for ( j = 0; j <find_real_child[i].size() ; j++)
			{
				child=find_real_child[i][j];
				if (low[child]==low[father[i]])
				{
					flag=0;
				}
			}
			if (flag)
			{
				node[node_amount].subnet++;
			}
			node_amount++;
		}
	}
	if (child_num[root]>=2)
	{
		node[node_amount].node_num=root;
		node[node_amount].subnet=child_num[root];
		node_amount++;
	}
}
int cmp(struct NODE a,struct NODE b)
{
	return a.node_num<b.node_num;
}

int main()
{
	int a,b,i,flag=1,case_num=1;
	while (1)
	{
		for ( i = 1; i <=1000 ; i++)
		{
			f[i].clear();
			find_real_child[i].clear();
		}
		memset(relative_num,0,sizeof(relative_num));
		memset(child_num,0,sizeof(child_num));
		memset(d,-1,sizeof(d));
		memset(low,-1,sizeof(low));
		memset(visited,0,sizeof(visited));
		memset(father,-1,sizeof(father));
		memset(node,0,sizeof(node));
		memset(whether_cut,0,sizeof(whether_cut));
		dfs_time=0;
		node_amount=0;
		for(i=1;;i++)//输入
		{
			scanf("%d",&a);
			if (a==0)
			{
				if (i==1)
					flag=0;
				break;
			}
			scanf("%d",&b);
			f[a].push_back(b);relative_num[a]++;//双向存树
			f[b].push_back(a);relative_num[b]++;
		}
		if (!flag)break;
		for ( i = 1; i <=1000 ; i++)
		{
			if (relative_num[i]!=0)//找到第一个在此连通图中的结点,即为根
			{
				root=i;
				tarjan_dfs(root);
				Count();
				break;
			}
		}
		sort(node,node+node_amount,cmp);
		printf("Network #%d\n",case_num++);
		if (node_amount==0)
		{
			printf("  No SPF nodes\n");
		}
		for ( i = 0; i < node_amount; i++)
		{
			printf("  SPF node %d leaves %d subnets\n",node[i].node_num,node[i].subnet);
		}
		printf("\n");
	}

	return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值