BZOJ1123-BLO——强连通分量求割点+计数

【题目描述】

Byteotia城市有n个 towns m条双向roads. 每条 road 连接 两个不同的 towns ,没有重复的road. 所有towns连通。

Input

输入n<=100000 m<=500000及m条边

Output

输出n个数,代表如果把第i个点去掉,将有多少对点不能互通。

Sample Input

5 5
1 2
2 3
1 3
3 4
4 5

Sample Output

8
8
16
14
8

【题目分析】
题目让求删去每个点以后会形成多少不能联通的城市对。
我们很容易联想到强连通分量求割点,割点肯定会形成很多个不能连通的城市对,而不是割点的话除了自身就没有其他不能连通的。(仔细研究样例就能发现自身和其他城市也算在内)
可是问题是对于每一个割点怎么来求多少个城市呢?
首先:割点上不同子树之间肯定无法连通
其次:割点的子树和除割点外的其他点无法联通
最后:对于每个被删除的点都和其他点无法联通
需要注意的是每个点对都要计算两次(这也是研究样例得到的)
最后一个很简单,就是(n-1)*2(我们后面的讨论都不讨论乘2,最后统一乘起来就是)
其次中的我们只需要统计每个割点的所有子树上总共有多少个点tmp,那么(n-1-tmp)*tmp就是答案
重点来看对于首先应该怎样解决。我们不妨现想一下如何统计每个割点的子树的节点,肯定是在搜索子树后再回溯得到每一棵子树的节点的个数,然后加起来。对于当前这个子树(节点数num[v]),他和前面所有的子树节点(节点数tmp=num[v1]+num[v2]+…+num[vi-1])都不连通,所以我们让答案加上num[v]和前面子树节点和的乘积,再更新子树节点和就好(详见代码Trajan函数部分)
【AC代码】

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<iostream>
#include<cmath>
#include<climits>
#include<queue>
#include<vector>
#include<set>
#include<map>
using namespace std;

typedef long long ll;
const int INF=0x3f3f3f3f;
const int MAXN=1e5+5;
const int MAXM=5e5+5;
struct node
{
    int v,next;
}Edge[MAXM<<1];
int head[MAXN],tot;
int DFN[MAXN],LOW[MAXN];
//bool cnt[MAXN];
int vis[MAXN],Time;
ll ans[MAXN],n,m;
ll num[MAXN];

void init()
{
    memset(head,0,sizeof(head));
    tot=0;
    memset(vis,0,sizeof(vis));
    Time=0;
    //memset(cnt,0,sizeof(cnt));
    memset(ans,0,sizeof(ans));
    memset(num,0,sizeof(num));
}

void AddEdge(int u,int v)
{
    tot++;
    Edge[tot].v=v; Edge[tot].next=head[u];
    head[u]=tot;
}

void read()
{
    int u,v;
    for(int i=0;i<m;i++)
    {
        scanf("%d%d",&u,&v);
        AddEdge(u,v); AddEdge(v,u);
    }
}

void Trajan(int x,int fa)
{
    int v; ll tmp=0;
    int son=0;
    DFN[x]=LOW[x]=++Time;
    vis[x]=1; num[x]=1;
    for(int i=head[x];i;i=Edge[i].next)
    {
        v=Edge[i].v;
        if(vis[v]==0)
        {
            Trajan(v,x);
            num[x]+=num[v];
            son++;
            LOW[x]=min(LOW[x],LOW[v]);
            //if(x==root &&son>1 || x!=root && LOW[v]>=DFN[x])
            if(LOW[v]>=DFN[x])  //这里不需要判断是否是根节点,因为如果如果是根节点只有一个子树的话tmp为0,ans[x]为0,要是有多个子树就是割点,直接计算。
            {
                ans[x]+=tmp*num[v];
                tmp+=num[v];
                //cnt[x]=true;
            }
        }
        else if(vis[v]==1)
        {
            LOW[x]=min(LOW[x],DFN[v]);
        }
    }
    vis[x]==2;
    ans[x]+=tmp*(n-tmp-1);
}

void solve()
{
    for(int i=1;i<=n;i++)
    {
        if(!vis[i])
        {
            Trajan(i,i);
        }
    }
}

void print()
{
    for(int i=1;i<=n;i++)
    {
        printf("%lld\n",2ll*(ans[i]+n-1));
    }
}

int main()
{
    while(~scanf("%lld%lld",&n,&m))
    {
        init();
        read();
        solve();
        print();
    }

    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值