Tarjan 做题总结

这两天Tarjan复习完后把题做了做。洛谷题单《图的连通性》已经做得差不多了。大部分是Tarjan的题,所以写一篇小总结。

T1 【模板】 缩点

不多bb。我已经写过关于Tarjan模板的随笔了。传送门

T2 【模板】割点

不多bb。传送门

T3 [USACO03FALL][HAOI2006]受欢迎的牛 G

题意简述:找到出度为0的点/强连通分量,并输出大小。

-----------------------------------------------------------------------------------

先缩点。值得注意的是图中只能有一个出度为0的点。如果有更多的出度为0的点那这些出度为0的点无法互相到达,那么数量就会为0。

数量在弹栈的时候记录就好。

代码:

#include<bits/stdc++.h>
using namespace std;
int head[250005],shabi;
int st[250005],top;
int vis[250005],dfn[250005],low[250005],all[250005],color[250005],du[250005];
int n,m,cnt,tot,x,y,k;
struct node
{
    int next,to;
}edge[550005];
void add(int from,int to)
{
    edge[++shabi].next=head[from];
    edge[shabi].to=to;
    head[from]=shabi;
}
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){
        if (ch=='-') f=-1;
        ch=getchar(); 
    }while('0'<=ch&&ch<='9'){
        x=x*10+ch-'0';
        ch=getchar();
    }
    return x*f;
}
void tarjan(int now)
{
    low[now]=dfn[now]=++cnt;
    vis[now]=1;
    st[++top]=now;
    for (int i=head[now];i;i=edge[i].next)
    {
        int to=edge[i].to;
        if (!dfn[to]) tarjan(to),low[now]=min(low[now],low[to]);
        else if (vis[to]) low[now]=min(low[now],dfn[to]);
    }
    if (dfn[now]==low[now]){
        tot++;
        do{
             k=st[top];top--;
            color[k]=tot;
            vis[k]=0;
            all[tot]++;
        }while(k!=now);
    }
}
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);
    for (int w=1;w<=n;w++)
        for (int i=head[w];i;i=edge[i].next)
        {
            int to=edge[i].to;
            if (color[w]!=color[to]) du[color[w]]++;
        }
    int flag=0;
    for (int i=1;i<=tot;i++)
    {
        if (!du[i]){
            if (flag){
                cout<<0;
                return 0;
            }
            flag=i;
        }
    }
    cout<<all[flag];
    return 0;
}

T4 [USACO06JAN]The Cow Prom S

Tarjan裸题。只要把强连通分量的大小记录下来,如果$num[i]geq 2$那么$ans++$。输出$ans$即可。

代码:

#include<bits/stdc++.h>
using namespace std;
const int maxn=10005;
const int maxm=50005;
int dfn[maxn],low[maxn],vis[maxn],num[maxn],pos[maxn],cnt,tot;
int st[maxn],top;
int head[maxm],jishu;
int n,m,ans;
struct node
{
    int next,to;
}edge[maxm];
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
    while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
inline void add(int from,int to)
{
    edge[++jishu].next=head[from];
    edge[jishu].to=to;
    head[from]=jishu;
}
void tarjan(int now)
{
    vis[now]=1;
    st[++top]=now;
    dfn[now]=low[now]=++cnt;
    for (int i=head[now];i;i=edge[i].next)
    {
        int to=edge[i].to;
        if (!dfn[to]) tarjan(to),low[now]=min(low[now],low[to]);
        else if (vis[to]) low[now]=min(low[now],dfn[to]);
    }
    if (dfn[now]==low[now])
    {
        tot++;
        while(st[top+1]!=now)
        {
            pos[st[top]]=tot;
            num[tot]++;
            vis[st[top--]]=0;
        }
    }
}
int main()
{
    n=read(),m=read();
    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);
    for (int i=1;i<=tot;i++) if (num[i]>1) ans++;
    printf("%d",ans);
    return 0;
}

T5 [USACO5.3]校园网Network of Schools

子问题1就是求缩点后入度为零的点的数量。子问题2就是求$max(ans1,ans2)$。

仔细读读题还是能明白要干什么的。

代码:

#include<bits/stdc++.h>
using namespace std;
const int maxn=10005;
const int maxm=50005;
int dfn[maxn],low[maxn],vis[maxn],pos[maxn],cnt,tot;
int st[maxn],top;
int head[maxm],jishu;
int n,fat[maxn][2],k,in[maxn],out[maxn];
struct node
{
    int next,to;
}edge[maxm];
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
    while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
inline void add(int from,int to)
{
    edge[++jishu].next=head[from];
    edge[jishu].to=to;
    head[from]=jishu;
}
void tarjan(int now)
{
    vis[now]=1;st[++top]=now;
    dfn[now]=low[now]=++cnt;
    for (int i=head[now];i;i=edge[i].next)
    {
        int to=edge[i].to;
        if (!dfn[to]) tarjan(to),low[now]=min(low[now],low[to]);
        else if (vis[to]) low[now]=min(low[now],dfn[to]);
    }
    if (dfn[now]==low[now])
    {
        tot++;
        while(st[top+1]!=now)
        {
            pos[st[top]]=tot;
            vis[st[top--]]=0;
        }
    }
}
int main()
{
    n=read();
    for (int i=1;i<=n;i++)
    {
        int x=read();
        while(x!=0)
        {
            add(i,x);
            fat[++k][0]=i;
            fat[k][1]=x;
            x=read();
        }
    }
    for (int i=1;i<=n;i++) if (!dfn[i]) tarjan(i);
    for (int i=1;i<=k;i++)
        if (pos[fat[i][0]]!=pos[fat[i][1]]) in[pos[fat[i][1]]]++,out[pos[fat[i][0]]]++;
    int ans1=0,ans2=0;
    for (int i=1;i<=tot;i++)
    {
        if (in[i]==0) ans1++;
        if (out[i]==0) ans2++;
    }
    if (tot==1) cout<<1<<endl<<0;
    else cout<<ans1<<endl<<max(ans1,ans2);
    return 0;
}

T6 上白泽慧音

在弹栈的时候记录强连通分量的大小和字典序就行了。

代码:

#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+7;
const int inf=0x3f3f3f3f;
struct note{
    int to,nex;
}e[maxn*3];
int col,num,head[maxn],dfn[maxn],low[maxn],de[maxn],co[maxn],si[maxn],stk[maxn];
int top,n,m;
int cnt=-1;
void add(int x,int y)
{
    static int cnt=0;
    cnt++;
    e[cnt].to=y;
    e[cnt].nex=head[x];
    head[x]=cnt;
}

void tarjan(int u)
{
    dfn[u]=low[u]=++num;
    stk[++top]=u;
    for(int i=head[u];i;i=e[i].nex)
    {
        int v=e[i].to;
        if(!dfn[v]){
            tarjan(v);
            low[u]=min(low[u],low[v]);
        }
        else if(!co[v])low[u]=min(low[u],dfn[v]);
    }
    if(low[u]==dfn[u])
    {
        co[u]=++col;
        ++si[col];
        while(stk[top]!=u)
        {
            ++si[col];
            co[stk[top]]=col;
            --top;
        }
        --top;
    }
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
    {
        int x,y,f;
        cin>>x>>y>>f;
        if(f==1)add(x,y);
        if(f==2)add(x,y),add(y,x);
    }
    for(int i=1;i<=n;i++)if(!dfn[i])tarjan(i);
    for(int i=1;i<=col;i++)cnt=max(cnt,si[i]);
    cout<<cnt<<endl;
    for(int i=1;i<=n;i++)
    {
        if(si[co[i]]==cnt)
        {
           int now=co[i]; 
           for(int j=i;j<=n;j++)if(co[j]==now)cout<<j<<" ";
           return 0; 
        }
    }
    return 0;
}

后记:裸的Tarjan题还是很好看出解法的,但是和其他图论算法结合起来就十分难受了,有时候真的是绞尽脑汁也想不出来。

原文地址:https://www.cnblogs.com/Invictus-Ocean/p/12513988.html