背景
在昨天的NOIP模拟赛中,zyz与qhy出了一道tarjan的裸题,然而我不会就DFS了50分。最后虽然rank1但是分数很低。
算法描述
略。
模板
仅作者可见。
题目
CodeVS1332 上白泽慧音
裸题,统计一个size表示这个强联通分量的大小。
//tarjan求强连通分量
#include <cstdio>
#include <algorithm>
#include <queue>
#include <vector>
#define maxn 110000
using namespace std;
int head[maxn], to[maxn], next[maxn], tid[maxn], tot, tim, size[maxn], s[maxn],
dfn[maxn], low[maxn], vis[maxn], cnt, top;
priority_queue<int,vector<int>,greater<int> > heap;
void adde(int a, int b){to[++tot]=b;next[tot]=head[a];head[a]=tot;}
void tarjan(int pos)
{
int p;
dfn[pos]=low[pos]=++tim;
s[++top]=pos;
vis[pos]=1;
for(p=head[pos];p;p=next[p])
{
if(vis[to[p]]==0)tarjan(to[p]);
if(vis[to[p]]==1)low[pos]=min(low[pos],low[to[p]]);
}
if(dfn[pos]==low[pos])
for(cnt++;s[top+1]!=pos;top--)
tid[s[top]]=cnt, vis[s[top]]=2;
}
int main()
{
int N, M, a, b, t, i;
scanf("%d%d",&N,&M);
for(i=1;i<=M;i++)
{
scanf("%d%d%d",&a,&b,&t);
adde(a,b);if(t==2)adde(b,a);
}
for(i=1;i<=N;i++)if(vis[i]==0)tarjan(i);
for(i=1;i<=N;i++)size[tid[i]]++;
for(i=1,t=0;i<=N;i++)
if(size[tid[i]]>size[tid[t]])t=i;
for(i=1;i<=N;i++)if(tid[i]==tid[t])heap.push(i);
printf("%d\n",heap.size());
while(!heap.empty())printf("%d ",heap.top()),heap.pop();
return 0;
}
CodeVS2822 爱在心中
就是说缩点之后问你有没有一个点它是所有点都能到达的,直接dfs就好了。
//tarjan求强连通分量
#include <cstdio>
#include <algorithm>
#define maxn 2000000
using namespace std;
int N, M, head[maxn], to[maxn], next[maxn], tot, tid[maxn], dfn[maxn], s[maxn], top,
low[maxn], vis[maxn], size[maxn], tim, td[maxn], cnt, can[maxn];
void adde(int a, int b){to[++tot]=b;next[tot]=head[a];head[a]=tot;}
void tarjan(int pos)
{
int p;
dfn[pos]=low[pos]=++tim;
s[++top]=pos;vis[pos]=1;
for(p=head[pos];p;p=next[p])
{
if(vis[to[p]]==0)tarjan(to[p]);
if(vis[to[p]]==1)low[pos]=min(low[pos],low[to[p]]);
}
if(dfn[pos]==low[pos])
for(cnt++;s[top+1]!=pos;top--)
tid[s[top]]=cnt,size[cnt]++,vis[s[top]]=2;
}
void dfs(int pos, int t)
{
int p;
vis[pos]=1;
if(tid[pos]==t)can[tid[pos]]=1;
for(p=head[pos];p;p=next[p])
{
if(vis[to[p]]==0)dfs(to[p],t);
can[tid[pos]]=can[tid[pos]] or can[tid[to[p]]];
}
}
int main()
{
int i, a, b, x, j;
scanf("%d%d",&N,&M);
for(i=1;i<=M;i++)scanf("%d%d",&a,&b),adde(a,b);
for(i=1;i<=N;i++)if(vis[i]==0)tarjan(i);
for(x=cnt,i=1;i<=cnt;i++)x-=size[i]==1;
printf("%d\n",x);
x=0;
for(i=1;i<=cnt;i++)
{
if(size[i]==1)continue;
for(j=1;j<=cnt;j++)can[j]=0;
for(j=1;j<=N;j++)vis[j]=0;
for(j=1;j<=N;j++)if(vis[j]==0)dfs(j,i);
for(j=1;j<=cnt and can[j];j++);
if(j>cnt)
{
x=1;
for(j=1;j<=N;j++)if(tid[j]==i)printf("%d ",j);
}
}
if(x==0)printf("-1\n");
return 0;
}
luoguP2746 [USACO5.3]校园网Network of Schools
缩点之后统计所有点的入度出度,第一问就是输出入度为0的点的个数。
第二问输出max(入度为0的点的个数,出度为0的点的个数)+连通块个数
对于第二问:对于一张有向无环图,把入度为0的点的集合叫做{S},那出度为0的点的集合叫做{T},那么以{S}中的每一个点为起点DFS,肯定能遍历{T}中的所有点。那么我们想要把这张图变成一个环。首先考虑card(S)<card(T)的情况,这时任意t∈{T},一定对应至少一个s∈{S},满足s能到达t,于是我们就连一条边t->s,那么s与t就形成了一个环,对于每一个t连出一条这样的边,那么总边数就是card(T),这个时候,如果原先的图是一个连通块,答案就是card(T),否则还要加上连通块个数。当card(S)>=card(T)时同理。
//Tarjan求强连通分量
#include <cstdio>
#include <algorithm>
#define maxn 100000
using namespace std;
int N, M, head[maxn], to[maxn], next[maxn], rd[maxn], tid[maxn], tim, cnt, dfn[maxn],
low[maxn], etot, top, s[maxn], vis[maxn], cd[maxn];
void adde(int a, int b){to[++etot]=b;next[etot]=head[a];head[a]=etot;}
void tarjan(int pos)
{
int p;
dfn[pos]=low[pos]=++tim;
s[++top]=pos;vis[pos]=1;
for(p=head[pos];p;p=next[p])
{
if(vis[to[p]]==0)tarjan(to[p]);
if(vis[to[p]]==1)low[pos]=min(low[pos],low[to[p]]);
}
if(dfn[pos]==low[pos])
for(cnt++;s[top+1]!=pos;top--)
tid[s[top]]=cnt,vis[s[top]]=2;
}
void shrink()
{
int p, i;
for(i=1;i<=N;i++)
for(p=head[i];p;p=next[p])
if(tid[i]^tid[to[p]])
cd[tid[i]]++,rd[tid[to[p]]]++;
}
void input()
{
int i, t;
scanf("%d",&N);
for(i=1;i<=N;i++)for(scanf("%d",&t);t;scanf("%d",&t))adde(i,t);
}
void print()
{
int cnt1=0, cnt2=0, i;
for(i=1;i<=cnt;i++)cnt1+=rd[i]==0,cnt2+=cd[i]==0;
printf("%d\n%d",cnt1,max(cnt1,cnt2));
}
int main()
{
input();
for(int i=1;i<=N;i++)if(!vis[i])tarjan(i);
shrink();
if(cnt==1){printf("1\n0\n");return 0;}
print();
return 0;
}