ICPC2018 Nakhon Pathom-Communication(通讯)(Tarjan连通算法 || Floyd+并查集)

题目描述
The Ministry of Communication has an extremely wonderful message system, designed by the President himself. For maximum efficiency, each office in the Ministry can send a message directly to some, but not necessarily all, other offices. These connections are not always two-way, because sometimes one office should have the power to send a message to another office, but the other office should not have the power to send one back. This may seem unfair, or even illogical, to uneducated people, but this is the will of the President.

通讯部有一个好得不得了的通信系统,由总统自己所设计。为了最大效率,部门中每个办公室都可以直接给其他的某个办公室发信息,但没必要给所有人都发。这些连接并非都是双向的,因为有的时候一个办公室有权利给其他办公室发信息,但是那个办公室没权回信。这可能有点不公平,或者压根不符合逻辑,对于不了解的人来说。但这是总统的意愿。


There are so many offices now that the situation has become rather confusing, so the President has decided to reorganize the Ministry to be better adapted to the message system.

现在有许多的办公室那边的状况变得相当的麻烦,所以总统打算重组部门来更好应用这个信息系统。


The President will divide the Ministry into new departments based on two simple principles:

总统将会把这个部门分成新的部分,基于如下两个原则

If A and B are in the same department then A can transmit a message to B, and B can transmit a message to A.
If A can transmit a message to B, and B can transmit a message to A, then A and B are in the same department.
How many departments will the reorganized Ministry have?

如果A和B在相同部门内,那么A可以给B发信息,B也可以给A回信。如果A可以给B发信息,B可以给A回信,那么A和B在同一部门(充要条件),那么有重组后这个部门有多少个部分?
输入
Input starts with a line containing a single integer N, with 0 < N ≤ 100. This tells you how many test cases there will be.
Each following pair lines contains a single test case. The first line of each test case contains an integer n, with 1 < n ≤ 200. This is the number of offices the Ministry has. The second line starts with an integer e with 0 < e <n2/4. This tells you how many individual direct (and directed) connections the Ministry has. Following this are e pairs of integers a, b, with 0 ≤a < b ≤ n − 1. These pairs indicate that there is a connection from a to b. There is at most one direct connection going out from one office and into another.

输入以一行包含一个正整数N的数据开始,这代表着测试实例的个数。下面的每一组输入都包含一组测试数据。每个测试数据的第一行包含一个整数n,代表着这个部门有多少个办公室。第二行以一个整数e开始,这代表着这个部门里有多少的有向通讯。接下来是e对整数a,b。这些数对代表从A到B的连接。两个办公室间至多存在一组有向边。

输出
Each line of output is an integer saying how many departments the Ministry corresponding to that test case will have.

输出这个部门里有多少个通讯部分。

输入
3
6
2 0 5 5 0
5
7 0 1 0 2 1 0 1 3 2 4 3 1 4 2
3
4 0 1 0 2 1 0 1 2

样例输出
5
2
2

纵观题干,很容易就能看出这是一个求连通块个数的题目。这里介绍两种办法来解这个题:

Tarjan连通算法

我们知道,Tarjan算法的最主要功能之一就是在连通图里求出每组强连通分量,所以在这个题目中,自然可以运用它来求出每一组相互连通的数据。(运用这个算法的话,成环成链都无所谓)

我们粗略的解释一下Tarjan算法:就是遍历一条路径,把每个点打上时间戳(访问次序),以及初始化接触的最靠近这条路径根节点的点(以下简称接触点),然后递归,找到下一个点的接触点,一条路径找完之后,就可以比对接触点的关系了。(详细见代码)

思路大抵分为以下几步:

  1. 使用链式前向星建立有向图。
  2. 在for循环里,对于每个没有时间戳(未访问)的点用tarjan算法求出与之相关的连通块
#include <iostream>
#include <cstdio>
#include <bits/stdc++.h>
#include <map>
#include <algorithm>
#include <stack>
#include <iomanip>
#include <cstring>
#include <cmath>
#define DETERMINATION main
#define lldin(a) scanf("%lld",&a)
#define din(a) scanf("%d",&a)
#define printlnlld(a) printf("%lld\n",a)
#define printlnd(a) printf("%d\n",a)
#define printlld(a) printf("%lld",a)
#define printd(a) printf("%d",a)
#define reset(a,b) memset(a,b,sizeof(a))
const long long int INF=0x3f3f3f3f;
using namespace std;
const double PI=acos(-1);
typedef long long ll;
typedef unsigned long long ull;
typedef long double ld;
const int mod=1000000007;
const int tool_const=1999112620000907;
const int tool_const2=33;
inline ll lldcin()
{
    ll tmp=0,si=1;
    char c=getchar();
    while(c>'9'||c<'0')
    {
        if(c=='-')
            si=-1;
        c=getchar();
    }
    while(c>='0'&&c<='9')
    {
        tmp=tmp*10+c-'0';
        c=getchar();
    }
    return si*tmp;
}
///Schlacht von Stalingrad
/**Although there will be many obstructs ahead,
the desire for victory still fills you with determination..**/
ll current_turn=1;
ll secquence[50000],vis[50000],low_reach[50000],cnt=1,ans;
stack<ll>st;//这个栈可以用数组模拟
ll heads[50000];
struct node
{
    ll to,next;
} nodes[677777];
void initialization()//初始化函数
{
    cnt=1;
    reset(secquence,0);
    reset(vis,0);
    reset(low_reach,0);
    reset(heads,-1);
    ans=0;
}
void construction(ll from,ll to)
{
    nodes[cnt]=node{to,heads[from]};
    heads[from]=cnt++;
}
void tarjan(ll current)
{
    secquence[current]=current_turn;//打上时间戳
    low_reach[current]=current_turn;//初始化接触点
    current_turn++;//访问次序自增
    st.push(current);//压入栈中
    vis[current]=1;//在栈中的标记
    for(int i=heads[current]; i!=-1; i=nodes[i].next)
    {
        ll toward=nodes[i].to;
        if(secquence[toward]==0)//这个点未访问
        {
            tarjan(toward);
            low_reach[current]=min(low_reach[toward],low_reach[current]);
        }
        else if(vis[toward])//已在栈中
            low_reach[current]=min(low_reach[toward],low_reach[current]);
    }
    if(secquence[current]==low_reach[current])//构成连通分量
    {
        ans++;
        vis[current]=false;
        while(st.top()!=current)
        {
            vis[st.top()]=false;
            st.pop();
        }
        st.pop();
    }
}
int DETERMINATION()
{
    ll t;
    cin>>t;
    while(t--)
    {
        initialization();
        ll totality;
        cin>>totality;
        ll tmp;
        cin>>tmp;
        ll tmp2,tmp3;
        for(int i=1; i<=tmp; i++)
        {
            cin>>tmp2>>tmp3;
            tmp2++,tmp3++;
            construction(tmp2,tmp3);
        }
        for(int i=1; i<=totality; i++)
        {
            if(secquence[i]==0)
                tarjan(i);
        }
        cout<<ans<<endl;
    }
    return 0;
}

 Floyd+并查集

 连通块自然是每个元素之间相互连通,并查集也可以实现这样的功能。但是并查集并没有“有向连接”,而且单用并查集也难以判断间接连接(成环)这样的情况,所以加上Floyd算法为辅助来应对这样的需求。

我们用一个二维数组来代表某两个点的单项直接连通,并用一个二维数组代表这两个直接的距离(连通为常数,不连通为INF无穷大),这个距离数组是为Floyd算法准备的。 

Floyd算法类似于向量加法,凭借着点间距离的关系,将两个点直接相连的情况拆分为n个向量的和,而最终结果与单纯两点相连结果一样,用这样的办法可以判断间接连通。

我们用并查集数组代表每一个基本单位,它的融合代表着区域的连通。与其他点连通的情况应当是它与某个点拥有共同祖先,这个时候如果它的祖先是他自己,那么这个点就是孤立的或是一个强连通分量的公共祖先,这都代表一个连通块,都统计上即可。

并查集的相关知识在此不做赘述。

思路大体如下:

  1.  建立并查集数组并初始化,用结构体存储相关路线并用链式前向星建立有向图,同时用二维数组标明直接连通
  2. Floyd算法判断间接连通。
  3. 依据之前存储的路线,对各点关系进行判断,倘若根据二维数组判断出两点直接相连或两点彼此距离存在(非无穷大),那么把代表这些点的并查集元素融合。

以下是详细代码 

#include <iostream>
#include <cstdio>
#include <bits/stdc++.h>
#include <map>
#include <algorithm>
#include <stack>
#include <iomanip>
#include <cstring>
#include <cmath>
#define DETERMINATION main
#define lldin(a) scanf("%lld",&a)
#define din(a) scanf("%d",&a)
#define printlnlld(a) printf("%lld\n",a)
#define printlnd(a) printf("%d\n",a)
#define printlld(a) printf("%lld",a)
#define printd(a) printf("%d",a)
#define reset(a,b) memset(a,b,sizeof(a))
const long long int INF=0x3f3f3f3f;
using namespace std;
const double PI=acos(-1);
typedef long long ll;
typedef unsigned long long ull;
typedef long double ld;
const int mod=1000000007;
const int tool_const=1999112620000907;
const int tool_const2=33;
inline ll lldcin()
{
    ll tmp=0,si=1;
    char c=getchar();
    while(c>'9'||c<'0')
    {
        if(c=='-')
            si=-1;
        c=getchar();
    }
    while(c>='0'&&c<='9')
    {
        tmp=tmp*10+c-'0';
        c=getchar();
    }
    return si*tmp;
}
///Schlacht von Stalingrad
/**Although there will be many obstructs ahead,
the desire for victory still fills you with determination..**/
int trees[8600],distances[1200][1200],relationships[1200][1200];
bool vis[8600];
ll quest(ll x)
{
    return trees[x]==x?x:trees[x]=quest(trees[x]);//并查集-搜寻祖先
}
void blend(ll x,ll y)//并查集-合并
{
    ll hx=quest(x);
    ll hy=quest(y);
    if(hx!=hy)
        trees[hx]=hy;
}
struct stroage
{
    ll from,to;
} stroages[50000];
void initialization(ll limit)//初始化
{
    for(int i=1; i<=limit; i++)
        trees[i]=i;
    reset(distances,0x3f);
    reset(relationships,0x3f);
    reset(stroages,0);
}
int DETERMINATION()
{
    ll t;
    cin>>t;
    while(t--)
    {
        ll n;
        cin>>n;
        initialization(n);
        ll tmp;
        cin>>tmp;
        ll fro,toward;
        for(int i=1; i<=tmp; i++)
        {
            cin>>fro>>toward;
            fro++,toward++;
            stroages[i].from=fro;//储存路径
            stroages[i].to=toward;
            relationships[fro][toward]=1;//直接相连
            distances[fro][toward]=2;//距离存在
        }
        for(int k=1; k<=n; k++)//Floyd
            for(int i=1; i<=n; i++)
                for(int j=1; j<=n; j++)
                {
                    if(distances[i][j]>distances[i][k]+distances[k][j])
                        distances[i][j]=distances[i][k]+distances[k][j];
                }
        for(int i=1; i<=tmp; i++)
        {
            ll tmp1,tmp2;
            tmp1=stroages[i].from;
            tmp2=stroages[i].to;
            if(relationships[tmp2][tmp1]!=INF||(distances[tmp2][tmp1]!=INF&&distances[tmp1][tmp2]!=INF))//直接相连或者两者路径存在(间接连通)
               blend(tmp1,tmp2);
        }
        ll cnt=0;
        for(int i=1;i<=n;i++)
            if(trees[i]==i)
             {
                 cnt++;
                 //cout<<i<<endl;
             }
        cout<<cnt<<endl;
    }
    return 0;
}
/*
 3
 3
4 0 1 0 2 1 0 1 2
*/

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值