讲解
一道模板题
牛的舞会The Cow Prom
#include<iostream>
#include<cstdio>
using namespace std;
int n,m,to[1000001],nxt[100001],head[1000001];
int edge,dfn[100001],low[100001],xx,yy,ans,top,time;
bool in[1000001];
int duilie[100001],mmp,pc;
void tarjan(int pccc)
{
dfn[pccc]=low[pccc]=++time;
duilie[++top]=pccc;
in[pccc]=1;
for(int i=head[pccc];i;i=nxt[i])
{
int t=to[i];
if(!dfn[t])//没有走到过
{
tarjan(t);
if(low[t]<low[pccc])
low[pccc]=low[t];
}
else//这个点之前已经走过了,并且是当前节点的祖先
{
if(low[pccc]>dfn[t]&&in[t]==1)
low[pccc]=dfn[t];//用t的dfn值更新,low值好像也行。。
}
}
if(dfn[pccc]==low[pccc])//出栈
{
pc=0;
while(duilie[top]!=pccc)
{
in[duilie[top]]=0;
top--;
pc++;
}
top--;
in[duilie[top]]=0;
if(pc>=1) ans++;
}
}
int main()
{
// freopen("1.txt","r",stdin);
cin>>n>>m;
for(int i=1;i<=m;i++)//建图
{
scanf("%d%d",&xx,&yy);
edge++;
to[edge]=yy;
nxt[edge]=head[xx];
head[xx]=edge;
}
for(int i=1;i<=n;i++)//有些点是不连通的
{
if(!dfn[i])
tarjan(i);
}
printf("%d",ans);
}
洛谷上一个大神写的,我和他的思路很像qwq
感觉他写的更好。
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#define N 100001
using namespace std;
bool vis[N];
int n,m,x,y,tim,tot,top,sum;
int head[N],dfn[N],low[N],stack[N],belong[N];
inline int read()
{
int x=0,f=1; char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-') f=-1; ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0'; ch=getchar();}
return x*f;
}
struct Edge
{
int from,next,to;
}edge[N];
int add(int x,int y)
{
tot++;
edge[tot].to=y;
edge[tot].next=head[x];
head[x]=tot;
}
int tarjan(int now)
{
dfn[now]=low[now]=++tim;
stack[++top]=now;vis[now]=true;
for(int i=head[now];i;i=edge[i].next)
{
int t=edge[i].to;
if(vis[t]) low[now]=min(low[now],dfn[t]);
else if(!dfn[t]) tarjan(t),low[now]=min(low[now],low[t]);
}
if(low[now]==dfn[now])
{
sum++;belong[now]=sum;
int ans=1;
for(;stack[top]!=now;top--)
{
vis[stack[top]]=false;
belong[stack[top]]=sum;
ans++;
}
vis[now]=false;top--;
if(ans==1) sum--;
}
}
int main()
{
n=read(),m=read();
for(int i=1;i<=m;i++)
x=read(),y=read(),add(x,y);
for(int i=1;i<=n;i++)
if(!dfn[i]) tarjan(i);
printf("%d",sum);
return 0;
}
缩点
刻录光盘
应该是个假缩点。。
tarjan一遍后遍历每个点和这个点连的所有边,记录好每一个点的belong,统计每一个强连通分量的入度,入度为0,ans++。qwq
我觉得我这个代码比上边的好。。
#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
int n,nxt[1000001],head[1000001],to[1000001],x,y,edge,time,belong[1000001],cnt,top;
int stack[100001],ru[1000001],ans,dfn[1000001],low[1000001],pc;
bool in[100001];
void add(int x,int y)
{
edge++;
to[edge]=y;
nxt[edge]=head[x];
head[x]=edge;
}
void tarjan(int x)
{
dfn[x]=low[x]=++time;
in[x]=1;stack[++top]=x;
for(int i=head[x];i;i=nxt[i])
{
int t=to[i];
if(!dfn[t])
{
tarjan(t);
low[x]=min(low[x],low[t]);
}
else
{
if(in[t]) low[x]=min(low[x],dfn[t]);
}
}
if(dfn[x]==low[x])
{
cnt++;
do
{
pc=stack[top];
belong[pc]=cnt;
in[pc]=0;
top--;
}
while(pc!=x);//非常容易出错的细节,这里的pc是上一个stack[top],而之后的一步top--,可以拿只有一个点的强连通分量模拟
}
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
while(scanf("%d",&x)&&x)
{
add(i,x);
}
}
for(int i=1;i<=n;i++)
{
if(!dfn[i]) tarjan(i);
}
for(int i=1;i<=n;i++)
{
for(int j=head[i];j;j=nxt[j])
{
int t=to[j];
if(belong[t]!=belong[i]) ru[belong[t]]++;
}
}
for(int i=1;i<=cnt;i++)
{
if(ru[i]==0) ans++;
}
printf("%d",ans);
}
消息扩散
和上题一样,一模一样。
#include<iostream>
#include<cstdio>
#define maxn 1500001
using namespace std;
int n,m,to[600001],nxt[600001],head[600001],dfn[maxn],low[maxn],belong[maxn],ru[maxn];
bool in[maxn];
int e,xx,yy,stack[600001],top,time,pc,cnt,ans;
void tarjan(int x)
{
stack[++top]=x;
dfn[x]=low[x]=++time;
in[x]=1;
for(int i=head[x];i;i=nxt[i])
{
int t=to[i];
if(!dfn[t])
{
tarjan(t);
low[x]=min(low[t],low[x]);
}
else
{
if(in[t])
{
low[x]=min(low[x],dfn[t]);
}
}
}
if(dfn[x]==low[x])
{
cnt++;
do
{
pc=stack[top];
belong[pc]=cnt;
top--;
in[pc]=0;
}
while(pc!=x);
}
}
int main()
{
cin>>n>>m;
for(int i=1;i<=m;i++)
{
scanf("%d%d",&xx,&yy);
e++;
to[e]=yy;
nxt[e]=head[xx];
head[xx]=e;
}
for(int i=1;i<=n;i++)
{
if(!dfn[i]) tarjan(i);
}
for(int i=1;i<=n;i++)
{
for(int j=head[i];j;j=nxt[j])
{
int t=to[j];
if(belong[t]!=belong[i])
ru[belong[t]]++;
}
}
for(int i=1;i<=cnt;i++)
{
if(ru[i]==0) ans++;
}
printf("%d",ans);
}