Gym-101630C Connections 强连通分量Tarjan算法或DFS/BFS

方法一:Tarjan算法

这道题改了好久好久好久...原理不理解果然什么都做不出来啊55555...

先附上大佬博客Orz:https://blog.csdn.net/sinat_35406909/article/details/81318725

Tarjan算法的for循环中,除了!dfn[v]的情况下要标记正向边,反向找边的时候有两种改法:

    else if(inst[v])
    {
        if(low[u]>dfn[v])
        {
            k=i;
            low[u]=dfn[v];
        }
    }
}
if(k!=-1)
    used[k]=true;

或者

    else if(inst[v])
    {
        if(low[u]>low[v])
            k=i; //注意在下一句(可能更改了low[u])的上面!
        if(low[u]>dfn[v])
            low[u]=dfn[v];
    }
}
if(k!=-1)
    used[k]=true;

这两种改法都过了,但我不是很懂这两种改发的区别。。因为自己Tarjan的原理都不怎么清楚55555...据某大佬说,一个是时间戳,一个是树根,应该是第二种比较合适,保留一条通往树根的路。如果有大佬能解释的比较清楚详尽一点,还请不吝赐教O(∩_∩)O

附上AC代码:

#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
#include<algorithm>
#include<vector>
#include<map>
#include<set>
#include<stack>
#include<queue>
using namespace std;
#define ll long long
typedef pair<int,int>pp;
#define mkp make_pair
#define pb push_back
const int MAXN=50010;
const int MAXM=100010;
int n,m;
struct Edge
{
    int from,to,next;
}edge[MAXM];
int head[MAXN],tot;
void init()
{
    tot=0;
    memset(head,-1,sizeof(head));
}
void addedge(int u,int v)
{
    edge[tot].from=u;edge[tot].to=v;
    edge[tot].next=head[u];head[u]=tot++;
}

int low[MAXN],dfn[MAXN];
int scc;
//int belong[MAXN];
int sta[MAXN],top;
int index;
bool inst[MAXN];
//int num[MAXN];

bool used[MAXM];
void Tarjan(int u)
{
    int v;
    low[u]=dfn[u]=++index;
    sta[top++]=u;
    inst[u]=true;
    int k=-1;
    for(int i=head[u];i!=-1;i=edge[i].next)
    {
        v=edge[i].to;
        if(!dfn[v])
        {
            Tarjan(v);
            used[i]=true;
            if(low[u]>low[v])
                low[u]=low[v];
        }
        else if(inst[v])
        {
            if(low[u]>dfn[v])
            {
                k=i;
                low[u]=dfn[v];
            }
        }
    }
    if(k!=-1)
        used[k]=true;
    if(low[u]==dfn[u])
    {
        scc++;
        do
        {
            v=sta[--top];
            inst[v]=false;
            //Belong[v]=scc;
            //num[scc]++;
        }
        while(v!=u);
    }
}


void solve()
{
    memset(dfn,0,sizeof(dfn));
    memset(inst,false,sizeof(inst));
    //memset(num,0,sizeof(num));
    index=scc=top=0;
    memset(used,false,sizeof(used));
    for(int i=1;i<=n;i++)
        if(!dfn[i])
            Tarjan(i);

    /*cout<<"scc="<<scc<<endl;
    for(int i=1;i<=n;i++)
        cout<<"i="<<i<<" dfn="<<dfn[i]<<" low="<<low[i]<<endl;*/
}
int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d",&n,&m);
        init();
        int u,v;
        for(int i=0;i<m;i++)
        {
            scanf("%d%d",&u,&v);
            addedge(u,v);
        }
        //cout<<"tot="<<tot<<endl;
        solve();

        int numm=0;
        for(int i=0;i<tot;i++)
        {
            if(!used[i])
            {
                printf("%d %d\n",edge[i].from,edge[i].to);
                numm++;
            }
            if(numm==(m-2*n))
                break;
        }
    }
	return 0;
}

方法二:DFS或BFS

其实就是正向搜一遍,反向搜一遍。据某大佬说是因为正边保证根能到所有其他点,反边的那颗树保证所有其他点能到根。但我一直不明白为什么要加两次边QAQ...

后来听说因为前向星这个数据结构很特殊,只记录一个点的所有出边,没法记录指向一个点的所有边,所以要再加一个edge2,记录所有入边。这样其实正向反向边都是原图中的边。我还是有点懵懵的QAQ...我真的太菜了55555...

附上AC代码:

#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
#include<algorithm>
#include<vector>
#include<map>
#include<set>
#include<stack>
#include<queue>
using namespace std;
#define ll long long
typedef pair<int,int>pp;
#define mkp make_pair
#define pb push_back

const int MAX=100010;
int n,m;
struct edge1
{
    int to,next;
}e1[MAX];//出边
int head1[MAX],tot1;
struct edge2
{
    int from,next;
}e2[MAX];//入边
int head2[MAX],tot2;
void init()
{
    tot1=tot2=0;
    memset(head1,-1,sizeof(head1));
    memset(head2,-1,sizeof(head2));
}
void addedge(int u,int v)
{
    e1[tot1].to=v;e1[tot1].next=head1[u];head1[u]=tot1++;//记录"出去"方向的边
    e2[tot2].from=u;e2[tot2].next=head2[v];head2[v]=tot2++;//记录指向该点的边
}

bool vis[MAX];
bool used[MAX];
void dfs1(int u)//正向树
{
    for(int i=head1[u];i!=-1;i=e1[i].next)
    {
        int v=e1[i].to;
        if(!vis[v])
        {
            vis[v]=true;
            used[i]=true;
            dfs1(v);
        }
    }
}
void dfs2(int v)//反向树
{
    for(int i=head2[v];i!=-1;i=e2[i].next)
    {
        int u=e2[i].from;
        if(!vis[u])
        {
            vis[u]=true;
            used[i]=true;
            dfs2(u);
        }
    }
}

int x[MAX],y[MAX];
int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d",&n,&m);
        memset(used,false,sizeof(used));
        init();
        for(int i=0;i<m;i++)
        {
            scanf("%d%d",&x[i],&y[i]);
            addedge(x[i],y[i]);
        }
        memset(vis,false,sizeof(vis));
        vis[1]=true;
        dfs1(1);

        memset(vis,false,sizeof(vis));
        vis[1]=true;
        dfs2(1);

        int numm=0;
        for(int i=0;i<m;i++)
        {
            if(!used[i])
            {
                printf("%d %d\n",x[i],y[i]);
                numm++;
            }
            if(numm==(m-2*n))
                break;
        }
    }
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值