蓝书P183
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
const int N=5005;
const int M=10005;
int first[N],dfn[N]={0},low[N],co[N],col=0,num=0;
struct node{
int v,next,from; //from表示无向边的另一条边的编号(可以以2为首边编号,则第n条无向边存储在下标为n*2,n*2+1的有向边结构体中,用d[i]=d[i^1]=1标记这条无向边已被访问过)
}e[M<<1];
bool vis[M<<1]={0}; //记录这条边是否被访问过
int n,m,k=0,st[N],top=0,du[N]={0};
int read(){
int s=0;
char ch=getchar();
while(ch<'0'||ch>'9') ch=getchar();
while(ch>='0'&&ch<='9'){ s=(s<<3)+(s<<1)+ch-'0';ch=getchar();}
return s;
}
void add(int ui,int vi){
e[++k].v=vi;e[k].next=first[ui];first[ui]=k;e[k].from=k+1; //u到v那条边,另一条边在k+1
e[++k].v=ui;e[k].next=first[vi];first[vi]=k;e[k].from=k-1; //v到u那条边,另一条边在k-1
}
int min(int x,int y){ if(x<y) return x;return y;}
void tarjan(int u){
low[u]=dfn[u]=++num;
st[++top]=u;
for(int i=first[u];i;i=e[i].next){
if(!vis[e[i].from]){ //如果是第一次访问这条无向边
vis[i]=1;
int v=e[i].v;
if(!dfn[v]) { //如果v点未被访问过
tarjan(v);
low[u]=min(low[u],low[v]);
}
else low[u]=min(low[u],dfn[v]);
}
else vis[i]=1; //第i条边已访问
}
int i;
if(low[u]==dfn[u]){
col++;
do{
i=st[top--];
co[i]=col; //将同一个强连通分量的点标记为痛
}while(i!=u);
}
}
void statis(){
for(int i=1;i<=n;++i)
for(int j=first[i];j;j=e[j].next){
if(vis[e[j].from]){
int v=e[j].v;
if(co[i]!=co[v]){ //边为i到v,若两个结点不在同一个强连通分量中
du[co[i]]++; //i结点所在的连通分量度加1
du[co[v]]++; //v结点所在的连通分量度加1
}
}
vis[j]=0; //第j条边标记操作过
}
int t=0; //t表示叶子结点个数
for(int i=1;i<=col;++i)
if(du[i]==1) t++; //如果度为1,则叶子结点个数加1
if(t==1) t=0; //如果叶子结点数为1,则只需要增加0条边
printf("%d\n",(t+1)/2);
}
int main(){
n=read();m=read();
memset(first,0,sizeof(first));
for(int i=1;i<=m;++i){
int u=read(),v=read();
add(u,v);
}
for(int i=1;i<=n;++i)
if(!dfn[i]) tarjan(i);
statis();
return 0;
}