https://www.cnblogs.com/nullzx/p/7968110.html(比较清晰的tarjan图示)
https://blog.csdn.net/STILLxjy/article/details/70176689(比较清晰的C++板子)
https://www.cnblogs.com/letlifestop/p/10262951.html(割点代码参考)
https://blog.csdn.net/lzc504603913/article/details/79993428(双连通分量)
tarjan属于dfs相关,蓝桥杯考的还是很多的
现在补一下板子,争取以后都能手敲出来tarjan
还有边的那个别用初始化head -1和~i了
以后用e[++cnt]和head初始为0的版本,比较好用
也不用把每个的下标可以减1了
开的数组虽多,但命名还算挺清楚的吧
计数器只会命tot num cnt的我枯了 这么多计数器
回头再总结一下割点、割边、双连通分量和边双联通分量的板子
其实就是魔改tarjan嘛,就是怕自己忘了
#include
#include
#include
using namespace std;
const int maxn=1e4+5;
const int maxm=5e4+5;
int n,m,u,v;
int res,ans;//最后答案 出度为0点的个数
int head[maxn],cnt;
int low[maxn],dfn[maxn],num;//最早非负祖先时间戳 时间戳
int stack[maxn],top,now;//用数组模拟栈 栈顶 当前栈顶值
int par[maxn],tot;//染色 颜色数
bool in[maxn];//是否在栈中
int out[maxn];//如果不在一个连通分量里 统计出度
int sum[maxn];//每个连通分量里的点的个数
struct edge
{
int to,next;
}e[maxm];
void add(int u,int v)
{
e[++cnt].to=v;
e[cnt].next=head[u];
head[u]=cnt;
}
void dfs(int u)
{
low[u]=dfn[u]=++num;
in[u]=1;
stack[++top]=u;
for(int i=head[u];i;i=e[i].next)
{
int v=e[i].to;
if(!dfn[v])
{
dfs(v);
low[u]=min(low[u],low[v]);
}
else if(in[v])
{
low[u]=min(low[u],dfn[v]);
}
}
if(low[u]==dfn[u])//环的第一个点
{
tot++;
do
{
now=stack[top--];
par[now]=tot;
sum[tot]++;
in[now]=0;
}while(now!=u);
}
}
void init()
{
memset(head,0,sizeof head);
memset(low,0,sizeof low);
memset(dfn,0,sizeof dfn);
memset(par,0,sizeof par);
memset(sum,0,sizeof sum);
memset(in,0,sizeof in);
memset(out,0,sizeof out);
res=ans=cnt=num=top=tot=0;
}
void solve()
{
for(int i=1;i<=n;++i)
if(!dfn[i])dfs(i);
for(int u=1;u<=n;++u)
{
for(int i=head[u];i;i=e[i].next)
{
int v=e[i].to;//u->v的有向边
//不在同一个连通分量里
if(par[u]!=par[v])out[par[u]]++;
}
}
for(int i=1;i<=tot;++i)//枚举连通分量
{
if(!out[i])//该连通分量 出度为0
{
ans++;
res=sum[i];
}
}
}
int main()
{
while(~scanf("%d%d",&n,&m))
{
init();
for(int i=1;i<=m;++i)
{
scanf("%d%d",&u,&v);
add(u,v);
}
solve();
printf("%d\n",ans==1?res:0);
}
return 0;
}
注意特判根节点,>=2棵从根节点起dfs的子树才是割点
#include
#include
#include
using namespace std;
const int maxn=110;
const int maxm=2e4+10;
int head[maxn],cnt;
int low[maxn],dfn[maxn],num;
int n,ans;
int u,v;
bool cut[maxn];//是否是割点
struct edge
{
int to,next;
}e[maxm];
void add(int u,int v)
{
e[++cnt].to=v;
e[cnt].next=head[u];
head[u]=cnt;
}
void init()
{
memset(cut,0,sizeof cut);
memset(head,0,sizeof head);
memset(low,0,sizeof low);
memset(dfn,0,sizeof dfn);
ans=num=cnt=0;
}
void dfs(int u,int fa)
{
low[u]=dfn[u]=++num;
int ch=0;
for(int i=head[u];i;i=e[i].next)
{
int v=e[i].to;
if(!dfn[v])
{
dfs(v,u);
ch++;//从u这里向下dfs的子树的数量
low[u]=min(low[u],low[v]);
if(low[v]>=dfn[u])cut[u]=1;//有一个 u就是割点
}
else if(v!=fa)
low[u]=min(low[u],dfn[v]);
}
if(fa==0&&ch==1)cut[u]=0;//特判根节点 有两棵及以上子树才可
}
int main()
{
while(~scanf("%d",&n)&&n)
{
init();
while(~scanf("%d",&u)&&u)
{
while((getchar())!='\n')
{
scanf("%d",&v);
add(u,v);add(v,u);
}
}
for(int i=1;i<=n;++i)
if(!dfn[i])dfs(i,0);
for(int i=1;i<=n;++i)
if(cut[i])ans++;
printf("%d\n",ans);
}
return 0;
}
#include
#include
#include
using namespace std;
const int maxn=1e5+10;
const int maxm=6e5+10;
int n,m,head[maxn];
int cnt=1;//这里是为了初始情况下,2和3的异或对应关系
int dfn[maxn],low[maxn],num;
bool bridge[maxm];
int ans,u,v;
struct edge
{
int to,next;
}e[maxm];
void add(int u,int v)
{
e[++cnt].to=v;
e[cnt].next=head[u];
head[u]=cnt;
}
void dfs(int u,int in)
{
low[u]=dfn[u]=++num;
for(int i=head[u];i;i=e[i].next)
{
int v=e[i].to;
if(!dfn[v])
{
dfs(v,i);
low[u]=min(low[u],low[v]);
if(low[v]>dfn[u])
bridge[i]=bridge[i^1]=1;
}
else if(i!=(in^1))
low[u]=min(low[u],dfn[v]);
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=m;++i)
{
scanf("%d%d",&u,&v);
add(u,v),add(v,u);
}
for(int i=1;i<=n;++i)
if(!dfn[i])dfs(i,0);
for(int i=2;i