bzoj 4243:交朋友 并查集

Description

你是活跃在历史的幕后的一名特工,为了世界的和平而日以继夜地努力着。
这个世界有N个国家,编号为1…N,你的目的是在这N个国家之间建立尽可能多的友好关系。你为了制定一个特工工作的计划,作出了一张当今国际关系的示意图。
你准备了一张非常大的画纸,先画下了代表每个国家的N个点。接下来,为了表示现在的国际关系,画下了M个连接两个国家的有向边,其中从国家a连向国家b的有向边(下面称作“边(a,b)”)表示“现在国家a向国家b派遣了大使”。这样就做出了N个点M条边的当今国际关系示意图。
作为两国友好关系的开端,两国之间需要进行“友好条约缔结会议”(以下简称会议)。如果某两个国家p和q要进行会议,那么需要一个向两国都派遣了大使的国家x作为中介。会议结束后,会议的双方相互向对方的国家派遣大使。换句话说,为了让国p和国q进行会议,必须存在一个国家x满足边(x,p)和边(x,q)都存在,并且在会议后添加两条边(p,q)和(q,p)(如果需要添加的某条边已经存在则不添加)。
你的工作是对于可以进行会议的两国,选择会议的中介并促使会议进行。使用这张图进行工作的模拟的话,世界距离和平还有多远的一个重要的基准就是这张图上的边数。也就是说,你想知道反复进行【选择两个国家使其进行会议】的工作后,图上的边数最多会到达多少。
现在给出国家的个数以及当今国际关系的情报,请你求出反复进行【选择两个国家使其进行会议】的工作后,图上的边数最多会到达多少。
Input

第一行两个空格分隔的整数N和M,分别表示世界上国家的个数和图中的边数
接下来M行描述画纸上的有向边的信息,其中第i行(1<=i<=M)有两个空格分隔的整数Ai和Bi,表示图中有一条从Ai到Bi的有向边(即Ai国向Bi国派遣了大使)。
Output

输出一行一个整数,表示能实现的边数的最大值。注意这个边数包括原有的边数和新连接的边数。
Sample Input

5 4

1 2

1 3

4 3

4 5
Sample Output

10
HINT

按照下面的顺序实现10条边:

以国1为中介,国2与国3进行会议;

以国4为中介,国3与国5进行会议;

以国3为中介,国2与国5进行会议。

1<=N<=10^5

1<=M<=2*10^5

1<=Ai<=N(1<=i<=M)

1<=Bi<=N(1<=i<=M)

Ai≠Bi(1<=i<=M)

(Ai,Bi)≠(Aj,Bj)(1<=i<j<=M)

Source

JOI 2013~2014 春季training合宿 竞技2 By PoPoQQQ

分析:
假设有一个点 p p p,显然他连出去的点 a 1 , a 2 , . . . . , a n a_1,a_2,....,a_n a1,a2,....,an将会连成一个完全子图。
然后把大于 1 1 1的完全子图的节点插入一个队列里。
枚举队列里的点,如果存在 x x x连向 y y y的一条边,显然 x x x中的所有点都可以有连向 y y y的双向边,所以可以合并 x x x y y y。如果 y y y是一个大小为 1 1 1的完全子图,则把他插入队列。

代码:

/**************************************************************
    Problem: 4243
    User: liangzihao
    Language: C++
    Result: Accepted
    Time:4904 ms
    Memory:8904 kb
****************************************************************/
 
#include <iostream>
#include <cstdio>
#include <cmath>
#include <queue>
#define LL long long
 
const int maxn=2e5+7;
 
using namespace std;
 
int n,m,x,y,cnt,pl;
int ls[maxn],vis[maxn],size[maxn],p[maxn];
LL ans;
 
struct edge{
    int y,next;
}g[maxn];
 
queue <int> q;
 
void add(int x,int y)
{
    g[++cnt]=(edge){y,ls[x]};
    ls[x]=cnt;
}
 
int find(int x)
{
    if (!p[x]) return x;
    return p[x]=find(p[x]);
}
 
void uni(int x,int y)
{
    int u=find(x),v=find(y);
    if (u==v) return;
    p[u]=v;
    size[v]+=size[u];
}
 
int main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<=m;i++)
    {
        scanf("%d%d",&x,&y);
        add(x,y);
    }
    for (int i=1;i<=n;i++)
    {
        p[i]=0;
        size[i]=1;
    }   
    for (int i=1;i<=n;i++)
    {
        pl=0;
        for (int j=ls[i];j>0;j=g[j].next)
        {
            int y=g[j].y;
            if (!pl) pl=y;
            else
            {
                uni(pl,y);
                if (!vis[y]) q.push(y),vis[y]=1;
                if (!vis[pl]) q.push(pl),vis[pl]=1;
            }
        }
    }   
    while (!q.empty())
    {
        int x=q.front();
        q.pop();
        for (int i=ls[x];i>0;i=g[i].next)
        {
            int y=g[i].y;
            uni(x,y);
            if (!vis[y]) q.push(y),vis[y]=1;
        }
    }   
    for (int i=1;i<=n;i++)
    {
        if (find(i)==i)
        {
            ans+=(LL)size[i]*((LL)size[i]-1);
        }
        for (int j=ls[i];j>0;j=g[j].next)
        {
            int y=g[j].y;
            if (find(i)!=find(y)) ans++;
        }
    }
    printf("%lld\n",ans);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值