<tarjan算法练习-缩点>codevs 2822 爱在心中

※题目链接:http://codevs.cn/problem/2822/
*题目大意:求一个有向图中节点个数不为一的SCC的个数,并且输出一个SCC中包含的节点(由小到大输出),要求这个SCC含有一个以上的节点并且其他所有的SCC都指向它
*首先用tarjan缩点,然后统计一下每一个SCC含的节点数,并记录,这个很好统计
*对于找被所有其他SCC指向的SCC:
①可以暴力求解,即从每一个SCC的其中一个点开始遍历,看是否能够到达所有的点,时间复杂度不会超,代码如下

    for(int i=1;i<=scc_cnt;++i)
    {
        if(comp[i].size()==1) continue;
        int tot=1;
        memset(vis,0,sizeof(vis));
        while(!q.empty()) q.pop();
        vis[comp[i][0]]=true;
        q.push(comp[i][0]);
        while(!q.empty())
        {
            int u=q.front();
            q.pop();
            for(int j=fist2[u];j!=-1;j=nxt2[j])
            {
                int v=e2[j].t;
                if(!vis[v])
                {
                    q.push(v);
                    vis[v]=true;
                    tot++;
                }
            }
        }
        if(tot==n)
        {
            sort(comp[i].begin(),comp[i].end());
            for(int j=0;j<comp[i].size();j++)
              printf("%d ",comp[i][j]);
            return 0;
        }
    }
    printf("-1");
    return 0;

②统计每一个SCC的出度。
1°一个SCC由其他所有点指向,当且仅当它的出度为0,否则会形成更大的环,不符合SCC的性质
2°若这个SCC只有一个节点,那么不成立
3°若有多个SCC出度为零,那么不成立(即不可能有任何一个SCC由所有的SCC指向)
代码如下:

for(int i=1;i<=scc_cnt;++i)
    {
        if(comp[i].size()>1) ans++;
        if(!du[i])
        {
            cnt=i;
            sum++;
        }
    }
    printf("%d\n",ans);
    if(sum==1&&comp[cnt].size()>1)
    {
        sort(comp[cnt].begin(),comp[cnt].end());
        for(int i=0;i<comp[cnt].size();++i)
          printf("%d ",comp[cnt][i]);
        return 0;
    }
    printf("-1");
    return 0;

所以整个题的代码:
1.暴力:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<stack>
#include<vector>
#include<queue>
using namespace std;

const int maxn=200000+10;
int n,m,cnt1,cnt2,scc_cnt,dfs_cnt,ans;
int fist1[maxn],nxt1[maxn<<1],fist2[maxn],nxt2[maxn<<1];
int dfn[maxn],low[maxn],Where[maxn];
bool vis[maxn];
struct hh
{
    int f,t;
}e1[maxn<<1];
struct hhh 
{
    int f,t;
}e2[maxn<<1];
stack<int>S;
vector<int>comp[maxn];
queue<int>q;

void build1(int f,int t)
{
    e1[++cnt1]=(hh){f,t};
    nxt1[cnt1]=fist1[f];
    fist1[f]=cnt1;
}
void build2(int f,int t)
{
    e2[++cnt2]=(hhh){f,t};
    nxt2[cnt2]=fist2[f];
    fist2[f]=cnt2;
}
void tarjan(int i)
{
    vis[i]=1;
    S.push(i);
    dfn[i]=low[i]=++dfs_cnt;
    for(int e=fist1[i];e!=-1;e=nxt1[e])
    {
        int j=e1[e].t;
        if(dfn[j]==-1)
        {
            tarjan(j);
            low[i]=min(low[i],low[j]);
        }
        else if(vis[j]) low[i]=min(low[i],dfn[j]);
    }
    if(low[i]==dfn[i])
    {
        int j;
        scc_cnt++;
        do
        {
            j=S.top();
            S.pop();
            vis[j]=0;
            comp[scc_cnt].push_back(j);
            Where[j]=scc_cnt;
        }
        while(i!=j);
    }
}
int main()
{
    memset(dfn,-1,sizeof(dfn));
    memset(fist1,-1,sizeof(fist1));
    memset(fist2,-1,sizeof(fist2));
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;++i)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        build1(x,y);
        build2(y,x);
    }
    for(int i=1;i<=n;++i)
      if(dfn[i]==-1) tarjan(i);
    for(int i=1;i<=scc_cnt;++i)
      if(comp[i].size()>=2) ans++;
    printf("%d\n",ans);
    for(int i=1;i<=scc_cnt;++i)
    {
        if(comp[i].size()==1) continue;
        int tot=1;
        memset(vis,0,sizeof(vis));
        while(!q.empty()) q.pop();
        vis[comp[i][0]]=true;
        q.push(comp[i][0]);
        while(!q.empty())
        {
            int u=q.front();
            q.pop();
            for(int j=fist2[u];j!=-1;j=nxt2[j])
            {
                int v=e2[j].t;
                if(!vis[v])
                {
                    q.push(v);
                    vis[v]=true;
                    tot++;
                }
            }
        }
        if(tot==n)
        {
            sort(comp[i].begin(),comp[i].end());
            for(int j=0;j<comp[i].size();j++)
              printf("%d ",comp[i][j]);
            return 0;
        }
    }
    printf("-1");
    return 0;
}

2.统计出度:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<stack>
#include<vector>
#include<queue>
using namespace std;

const int maxn=200000+10;
int n,m,cnt1,scc_cnt,dfs_cnt,ans,sum,cnt;
int fist[maxn],nxt[maxn<<1];
int dfn[maxn],low[maxn],Where[maxn],du[maxn];
bool vis[maxn];
struct hh
{
    int f,t;
}e[maxn<<1];
stack<int>S;
vector<int>comp[maxn];
queue<int>q;

void build(int f,int t)
{
    e[++cnt1]=(hh){f,t};
    nxt[cnt1]=fist[f];
    fist[f]=cnt1;
}
void tarjan(int i)
{
    vis[i]=1;
    S.push(i);
    dfn[i]=low[i]=++dfs_cnt;
    for(int x=fist[i];x!=-1;x=nxt[x])
    {
        int j=e[x].t;
        if(dfn[j]==-1)
        {
            tarjan(j);
            low[i]=min(low[i],low[j]);
        }
        else if(vis[j]) low[i]=min(low[i],dfn[j]);
    }
    if(low[i]==dfn[i])
    {
        int j;
        scc_cnt++;
        do
        {
            j=S.top();
            S.pop();
            vis[j]=0;
            comp[scc_cnt].push_back(j);
            Where[j]=scc_cnt;
        }
        while(i!=j);
    }
}
int main()
{
    memset(dfn,-1,sizeof(dfn));
    memset(fist,-1,sizeof(fist));
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;++i)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        build(x,y);
    }
    for(int i=1;i<=n;++i)
      if(dfn[i]==-1) tarjan(i);
    for(int i=1;i<=n;++i)
      for(int j=fist[i];j!=-1;j=nxt[j])
        if(Where[e[j].t]!=Where[i])
          du[Where[i]]++;
    for(int i=1;i<=scc_cnt;++i)
    {
        if(comp[i].size()>1) ans++;
        if(!du[i])
        {
            cnt=i;
            sum++;
        }
    }
    printf("%d\n",ans);
    if(sum==1&&comp[cnt].size()>1)
    {
        sort(comp[cnt].begin(),comp[cnt].end());
        for(int i=0;i<comp[cnt].size();++i)
          printf("%d ",comp[cnt][i]);
        return 0;
    }
    printf("-1");
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值