和剩余的连通分支数目 。
input
多组数据,EOF结束。
第1行:N和M和K 第2到第M+1行:每一行2个数Ui和Vi,表示Ui到Vi之间有一条边。
给定一个无向图(且可能不连通),删去一个点和与该点相连的所有边
图的连通性可能发生变化
本题考查连通分量的数目
如果删除了一个孤立的点,那么总的连通分量会减少
如果删除了一个非根割顶,那么总的连通分量会增加
增加多少个呢?
设置一个变量block[u],表示删除了u节点后,u原来所在的分支,将会分裂成多少棵分支 。
统计出原来的图中有多少个连通分支b,则删除一个点u之后,总的分支数目将变成b-1+block[u]。
试想,一个非根割顶被删,它的祖先会形成一个连通分量
同时它的每棵子树形成一个连通分量。
但是子树可能会和祖先连通,这种情况下,删除本割顶并没有将这样的子树独立出来,
这意味着block[u]对这样的子树不会增加
初始化block[u]:如果u是某根节点,block[u]=0(它已经没有长辈了),否则block[u]=1。
Tarjan中的tips:
void dfs(int u,int pa){
dfn[u]=low[u]=++t;
for(int i=0;i<edges[u].size();i++){
int v=edges[u][i];
if(v==pa)continue;
if(!low[v]){
dfs(v,u);
if(low[v]>=dfn[u])block[u]++;
else low[u]=min(low[u],low[v]);
}
else
low[u]=min(low[u],dfn[v]);
}
}
下面是wa的代码
void dfs(int u,int pa){
dfn[u]=low[u]=++t;
for(int i=0;i<edges[u].size();i++){
int v=edges[u][i];
if(v==pa)continue;
if(!low[v]){
dfs(v,u);
if(low[v]>=low[u])block[u]++;
}
low[u]=min(low[u],low[v]);
}
}
//low[u]是u节点能够追溯到的最早的时间,正确代码中的两处dfn[u]处均不能改成low[u]
//原因是
//low[u]可能已经被u的后向边更改
// 考虑下面的图,0到1,1到2,为方便起见,设初始时刻为0.
//2先去访问0,进行更新low[2]=0,2再去访问3,
//3最早能追溯到1,low[3]=1,根据wa的代码,这时候要比较if(low[v]>=low[u])block[u]++;
//我们惊讶地发现,block[2]成为了2,这意味着,把2删除后,会产生两个强连通分支。显然是错误的。
再考虑第二个位置被写错会发生什么事情:
假设我已经把第一个错误改正,我们得到下面的代码,它仍然是错误的:
void dfs(int u,int pa){
dfn[u]=low[u]=++t;
for(int i=0;i<edges[u].size();i++){
int v=edges[u][i];
if(v==pa)continue;
if(!low[v]){
dfs(v,u);
if(low[v]>=dfn[u])block[u]++;
else low[u]=min(low[u],low[v]);
}
else
low[u]=min(low[u],low[v]);
}
}
我们将使用下图:
开始
0到1,1到2, 2访问0, low[1]=0。
1到3, 3到4 ,4访问1,现在要执行low[u]=min(low[u],low[v]);
即low[4]=0。这样就更改了原图的拓扑关系。
工作继续,接下来会发生什么事情呢?
low[3]=0
然后,镜头回到1节点,关键的一步
判断if(low[v]>=dfn[u])block[u]++;
else low[u]=min(low[u],low[v]);
这时的low[3]=0,dfn[1]=1,block[1]不会增长,并且因为4节点已经访问过了,block[1]以后也不会有增长的机会了,
这意味着删除1节点,将会只得到1个连通分支,这同样是错误的。
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
using namespace std;
int N,M,k;
#define maxn 10000
vector<int> edges[maxn+10];
int block[maxn+10];
int dfn[maxn+10],low[maxn+10],t;
void dfs(int u,int pa){
dfn[u]=low[u]=++t;
for(int i=0;i<edges[u].size();i++){
int v=edges[u][i];
if(v==pa)continue;
if(!low[v]){
dfs(v,u);
if(low[v]>=dfn[u])block[u]++;
else low[u]=min(low[u],low[v]);
}
else
low[u]=min(low[u],dfn[v]);
}
}
int ran[maxn+10];
void init(){
for(int i=0;i<N;i++)edges[i].clear();
for(int i=0;i<N;i++)block[i]=1;
for(int i=0;i<N;i++)ran[i]=i;
}
int cmp(int a,int b){
if(block[a]>block[b])return 1;
else if(block[a]==block[b])return a<b;
return 0;
}
int b;
int main(){
while(scanf("%d%d%d",&N,&M,&k)==3){
init();
for(int i=0,u,v;i<M;i++){
scanf("%d%d",&u,&v);
edges[u].push_back(v);
edges[v].push_back(u);
}
t=b=0;
memset(dfn,0,sizeof(dfn));
memset(low,0,sizeof(low));
for(int i=0;i<N;i++){
if(!low[i]){
block[i]=0;
b++;
dfs(i,-1);
}
}
sort(ran,ran+N,cmp);
for(int i=0;i<k;i++){
printf("%d %d\n",ran[i],b-1+block[ran[i]]);
}
cout<<endl;
}
return 0;
}