51nod 1456 小K的技术【强连通Tarjan+缩点染色+并查集】好题~

本文探讨了一个关于在苏塞克王国中建立最少数量的传输管道的问题,通过使用Tarjan算法进行强连通分量分析,并结合并查集处理弱连通图,实现了对重要城市对间传输管道的有效构建。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目来源:  CodeForces
基准时间限制:1 秒 空间限制:131072 KB 分值: 80  难度:5级算法题

苏塞克王国是世界上创新技术的领先国家,在王国中有n个城市,标记为1到n。

由于小K的研究,我们最终能过在两个城市之间建立传输管道,一个传输管道能单向连接两个城市,即,一个从城市x到城市y的传输管道不能被用于从城市y传输到城市x。在每个城市之间的运输系统已经建立完善,因此,如果从城市x到城市y的管道和从城市y到城市z的管道都被已经被建立,人们能够立即从x到z。

小K也研究了国家政治,他认为在这m对城市(ai, bi) (1 ≤ i ≤ m)之间的传输尤其重要。他正在计划为每个重要城市对(ai, bi)建立传输管道,通过使用一个或多个传输管道,我们可以从城市ai到城市bi(但不需要从城市bi到城市ai)。我们要找出必须建立的传输管道的最小数。至今,还没有传输管道被建立,在每个城市之间也没有其他有效的传输方式。


对于第一个样例,其中一条最优路径如下图:


Input
第一行是两个以空格隔开的整数n和m(2 ≤ n ≤10^5 , 1 ≤ m ≤ 10^5 ),分别表示在苏塞克王国中的城市数和重要城市对的数。
之后m行描述重要城市对,第i行 (1 ≤ i ≤ m)包含两个以空格隔开的整数ai和bi(1 ≤ ai, bi ≤ n, ai ≠ bi),表示必须能通过一条或两条传输管道从城市ai到城市bi(但不需要从城市bi到城市ai),我们保证所有的城市对(ai, bi)是唯一的。
Output
输出满足小K目的所需要的传输管道的最小数。
Input示例
4 5
1 2
1 3
1 4
2 3
2 4
Output示例
3

思路(非常综合的一个题):


1、如果有环存在的强连通分量,对应其中有n个点,那么如果我们单单想要分配给这个强连通分量尽可能少的管道数,很明显就是n,但是如果我们此时有这样的情况(两个强连通分量有一条边需要将其连接的情况):

那么很明显,这条绿线是多余的,我们只要构造出来的图是一个大环,那么一共只需要花费6个管道即可。

那么结论:如果一个(弱)连通图中,包含n个顶点,包含强连通分量,那么对应整个图的建立需要n个管道。

如果不包含强连通分量,那么对应整个图的建立只需要n-1个管道。


2、那么我们首先跑一遍强连通Tarjan,将其全部染色并且缩点.再之后我们用并查集处理每一块连通图(弱连通图),对应发现其中如果有强连通分量存在的话,那么此连通图需要n个管道,否则就需要n-1个管道,累加统计即可。


3、细节比较多,而且代码实现不是很容易,大家耐心些预祝1A.~


Ac代码:

#include<stdio.h>
#include<string.h>
#include<vector>
using namespace std;
int n,m;
vector<int >mp[2000600];
int f[2000600];
int color[2000600];
int contz[2000600];
int flag[2000600];
int dfn[200600];
int low[200600];
int vis[200600];
int stack[200600];
int sig,cnt,tt;
int find(int a)
{
    int r=a;
    while(f[r]!=r)
        r=f[r];
    int i=a;
    int j;
    while(i!=r)
    {
        j=f[i];
        f[i]=r;
        i=j;
    }
    return r;
}
void merge(int a,int b)
{
    int A,B;
    A=find(a);
    B=find(b);
    if(A!=B)
    {
        f[B]=A;
        if(contz[a]>1||contz[b]>1)
        flag[A]=1;
        flag[A]+=flag[B];
    }
}
void Tarjan(int u)
{
    stack[++tt]=u;
    dfn[u]=low[u]=cnt++;
    vis[u]=1;
    for(int i=0;i<mp[u].size();i++)
    {
        int v=mp[u][i];
        if(vis[v]==0)
        {
            Tarjan(v);
            low[u]=min(low[u],low[v]);
        }
        if(vis[v]==1)low[u]=min(low[u],low[v]);
    }
    if(dfn[u]==low[u])
    {
        sig++;
        do
        {
            color[stack[tt]]=sig;
            vis[stack[tt]]=-1;
        }
        while(stack[tt--]!=u);
    }
}
void Slove()
{
    tt=-1;cnt=1;sig=0;
    memset(contz,0,sizeof(contz));
    memset(color,0,sizeof(color));
    memset(dfn,0,sizeof(dfn));
    memset(low,0,sizeof(low));
    memset(vis,0,sizeof(vis));
    memset(stack,0,sizeof(stack));
    for(int i=1;i<=n;i++)
    {
        if(vis[i]==0)Tarjan(i);
    }
    int output=0;
    for(int i=1;i<=sig;i++)f[i]=i,flag[i]=0;
    for(int i=1;i<=n;i++)contz[color[i]]++;
    for(int i=1;i<=sig;i++)
    {
        if(contz[i]>1)flag[i]=1;
    }
    for(int i=1;i<=n;i++)
    {
        for(int j=0;j<mp[i].size();j++)
        {
            int u=color[i];
            int v=color[mp[i][j]];
            if(u!=v)
            {
                if(find(u)!=find(v))
                {
                    merge(u,v);
                }
            }
        }
    }

    for(int i=1;i<=sig;i++)
    {
        find(i);
        if(f[i]==i)
        {
            if(flag[i]==0)output--;
        }
        output+=contz[i];
    }
    printf("%d\n",output);
}
int main()
{
    while(~scanf("%d%d",&n,&m))
    {
        for(int i=1;i<=n;i++)mp[i].clear();
        for(int i=0;i<m;i++)
        {
            int x,y;
            scanf("%d%d",&x,&y);
            mp[x].push_back(y);
        }
        Slove();
    }
}





评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值