ccf 201509-4 高速公路

题面

问题描述
  某国有n个城市,为了使得城市间的交通更便利,该国国王打算在城市之间修一些高速公路,由于经费限制,国王打算第一阶段先在部分城市之间修一些单向的高速公路。
  现在,大臣们帮国王拟了一个修高速公路的计划。看了计划后,国王发现,有些城市之间可以通过高速公路直接(不经过其他城市)或间接(经过一个或多个其他城市)到达,而有的却不能。如果城市A可以通过高速公路到达城市B,而且城市B也可以通过高速公路到达城市A,则这两个城市被称为便利城市对。
  国王想知道,在大臣们给他的计划中,有多少个便利城市对。
输入格式
  输入的第一行包含两个整数n, m,分别表示城市和单向高速公路的数量。
  接下来m行,每行两个整数a, b,表示城市a有一条单向的高速公路连向城市b。
输出格式
  输出一行,包含一个整数,表示便利城市对的数量。
样例输入
5 5
1 2
2 3
3 4
4 2
3 5
样例输出
3
样例说明

  城市间的连接如图所示。有3个便利城市对,它们分别是(2, 3), (2, 4), (3, 4),请注意(2, 3)和(3, 2)看成同一个便利城市对。
评测用例规模与约定
  前30%的评测用例满足1 ≤ n ≤ 100, 1 ≤ m ≤ 1000;
  前60%的评测用例满足1 ≤ n ≤ 1000, 1 ≤ m ≤ 10000;
  所有评测用例满足1 ≤ n ≤ 10000, 1 ≤ m ≤ 100000。

分析及思路:

本题是判断有向图中两个相互连通的点的对数,刚开始做的时候没学连通分量的算法,自己夏姬八推了一个结果连样例都没有过,只好爆搜了,对每个点dfs,用一个mp二维数组来保存一个点是否能到达另一个点,mp[i][j]==1代表i能到达j,就这样搜过了百分之六十,后来做完后去网上学了taijan算法,发现这道题就是tarjan板子题,直接套用tarjan,最后对每个强连通分量求一个组合数即可。

暴力dfs60分代码:

#include<iostream>
#include<cstring>
#include<queue>
#define rep(i,x,n) for(int i=x;i<n;i++)
#define per(i,x,n) for(int i=n-1;i>=x;i--)
using namespace std;
//head
typedef long long ll;
int n,m,a,b,cur;
short mp[10001][10001];
int head[10006],vis[10006];
struct Edge{int to,next;}edge[100006];
void addedge(){edge[cur].to=b;edge[cur].next=head[a];head[a]=cur++;}
ll bfs()
{
    ll cnt=0;
    queue<int>q;
    rep(i,1,n+1)
    {
        memset(vis,0,sizeof(vis));
        q.push(i);vis[i]=1;
        while(!q.empty())
        {
            int x=q.front();q.pop();
            for(int j=head[x];j!=-1;j=edge[j].next)
            {
                int nx=edge[j].to;
                if(!vis[nx])
                {
                q.push(nx);vis[nx]=1;mp[i][nx]=1;
                if(mp[i][nx]==1&&mp[nx][i]==1)cnt++;
                }
            }
        }
    }
    return cnt;
}
int main()
{
     ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
     memset(head,-1,sizeof(head));
     cin>>n>>m;
     rep(i,0,m)cin>>a>>b,addedge();
     cout<<bfs()<<endl;
     return 0;
}

tarjan算法满分代码:

#include<iostream>
#include<cstring>
#include<stack>
#define rep(i,x,n) for(int i=x;i<n;i++)
using namespace std;
int n, m, a, b, cnt, cur = 0, tot = 0, ans=0,head[10006], low[10006], dfn[10006],vis[10006];
struct Edge {
    int to, next;
} edge[100006];
void addedge() {
    edge[cur].to = b;
    edge[cur].next = head[a];
    head[a] = cur++;
}
stack<int>q;
int tarjan(int x) {
    low[x] = dfn[x] = ++tot;
    q.push(x);vis[x]=1;
    for(int i = head[x]; i != -1; i = edge[i].next) {
        int nx = edge[i].to;
        if(!dfn[nx]) {
            tarjan(nx);
            low[x] = min(low[x], low[nx]);
        } else if(vis[nx])
            low[x] = min(low[x], dfn[nx]);
    }
    if(low[x]==dfn[x])
    {
        cnt=0;
        while(1)
        {
            int tmp=q.top();
            q.pop();
            vis[tmp]=0;
            ++cnt;
            if(tmp==x)break;
        }
        if(cnt>1)ans+=(cnt*(cnt-1)/2);
    }
}
int main() {
    memset(head, -1, sizeof(head));
    cin >> n >> m;
    rep(i, 0, m)cin >> a >> b, addedge();
    rep(i,1,n+1)if(!dfn[i])tarjan(i);
    cout << ans << endl;
}

版权声明:本文为原创文章,转载请标明出处。
https://blog.csdn.net/u014390156

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值