对于任何一个网络,保证其稳定性都极其重要,如果在一个网络中有着一个点,使得这个点损坏了就会导致整个网络不连通,那么这个网络出现问题的风险就比较大,这个点我们称为割点。类似的有割边,即如果删除这个边,连通分量会增加。我们先来研究最基础的问题,一张无向连通图中割点的数量有几个。很明显,我们的第一反应是暴力做法,即每次删除一个节点,再dfs判断这张图的连通性,若删除之后不连通则为割点。我们发现这种做法的复杂度为O(v(v+e)),对于较大规模的图显得不够优秀,容易超时,于是我们来考虑另一种做法。
我们可以先对图进行一次dfs,得到一棵深搜生成树,接下来,我们来研究这棵树的性质。
1.对于根节点而言,如果它具有多棵子树,那么它一定是割点。因为从dfs的性质我们知道根节点的a子树一定不会有节点1连接到它的b子树的节点2上,假如有,那么节点2一定在子树a上,这就与前提矛盾了。所以,如果删除这个根节点,那么连通分量一定增加,故而是割点。
2.对于非根节点a而言,如果它存在一个子节点b,使得这个子节点b以及以这个子节点b为根的子树都没有与a的祖先相连接的边,那么节点a为割点。这个性质同样可以由dfs的性质证明。
那么,如何代码实现呢?
首先,对于根节点,我们容易进行判断,只要计算子树数量即可。对于非根节点就有一些复杂了,我们需要判断这个节点的子节点及其后代有无回退边,我们可以使用dfn作为计时器,计算该节点何时被dfs到(以判断亲缘关系),并将该值赋值给num数组。再建立一个low数组,表示该节点及其后代的所能连接到的最近的祖先是哪个节点,如果是其父亲节点的祖先,那么其父亲节点不是割点,否则是割点。
例题 poj1144
Time Limit: 1000MS | Memory Limit: 10000K | |
Total Submissions: 21824 | Accepted: 9612 |
Description
possible to reach through lines every other place, however it need not be a direct connection, it can go through several exchanges. From time to time the power supply fails at a place and then the exchange does not operate. The officials from TLC realized that in such a case it can happen that besides the fact that the place with the failure is unreachable, this can also cause that some other places cannot connect to each other. In such a case we will say the place (where the failure
occured) is critical. Now the officials are trying to write a program for finding the number of all such critical places. Help them.
Input
by one space. Each block ends with a line containing just 0. The last block has only one line with N = 0;
Output
Sample Input
5
5 1 2 3 4
0
6
2 1 3
5 4 6 2
0
0
Sample Output
1
2
Hint
#include<iostream>//poj不支持万能头哦 #include<cstdio> #include<vector> #include<cstring> using namespace std; const int maxn=105; vector<int>g[maxn]; int dfn=0; int num[maxn]; int low[maxn]; bool iscut[maxn]; int ans=0; void dfs(int x,int fa){//fa为x的父节点 num[x]=low[x]=++dfn;//便于判断继承关系 int child=0; for(int i=0;i<g[x].size();i++){ int v=g[x][i]; if(!num[v]){//判断是否dfs过 child++; dfs(v,x); low[x]=min(low[x],low[v]);//更新该节点及后代的最近祖先 if(low[v]>=num[x]&&x!=1){//注意是非根节点 iscut[x]=1; } } else if(num[x]>num[v]&&v!=fa){//判断回退边,注意父亲节点可以回退到,但是不算 low[x]=min(low[x],num[v]); } } if(x==1&&child>=2){//如果是根节点并且有多子树则为割点 iscut[x]=1; } } int main(){ ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); int n; cin>>n; while(n){ for(int i=1;i<=100;i++)g[i].clear(); memset(num,0,sizeof(num)); memset(low,0,sizeof(low)); memset(iscut,0,sizeof(iscut)); dfn=0,ans=0;//以上为初始化操作 string s; while(getline(cin,s)){//恶心的输入 s.push_back(' '); if(s[0]=='0')break; int x=0; int p; for(int i=0;i<s.size();i++){ if(s[i]>='0'&&s[i]<='9')x=x*10+s[i]-'0'; else{ p=i; break; } } int y=0; for(int i=p+1;i<s.size();i++){ if(s[i]>='0'&&s[i]<='9'){ y=y*10+s[i]-'0'; } else{ g[x].push_back(y);//建无向图 g[y].push_back(x); y=0; } } } dfs(1,-1);//以1为根节点dfs for(int i=1;i<=n;i++){ if(iscut[i])ans++;//割点数 } cout<<ans<<endl; cin>>n; } return 0; }
放假了,好久没有写博客了,摸一篇。