牛客练习赛56.小雀和他的王国 (无向图缩点+树的直径)

题目链接:https://ac.nowcoder.com/acm/contest/3566/E

题目大意:

给定一个无向图,初始时无向图内的任意两点均有若干条路径,现在你可以加一条边,使得下一次随机删除一条边时,删除的边让某两个城市变成无法连接的概率最小,求最小概率。

思路:

一眼就可以看出,如果删除的是桥,那么就会形成两个城市无法连接的情况。

所以我加边的时候就需要尽可能地让一些桥变成不是桥的路径。

怎么做呢?很明显,如果是一条链,链上的边都是桥,而我只需要将链首尾相连,就可以使得这条链上的桥都不再是桥。

而且我们注意到,Tarjan边双联通缩点之后,原图就变成了一颗树,树上的边都是桥,所以我们只需要在树上找到最长的链,将这条链上的边都变成不是桥的路径,这样我们就可以去除最多的桥,也是最优的策略了。

综上,先缩点建树,再求一遍树的直径,答案就出来了。

#include<bits/stdc++.h>
#include<vector>
#include<stack>
#define int long long
using namespace std;
typedef long long ll;
const int maxn=2e5+5;
const int mod=1e9+7;
typedef long long LL;
bool in[maxn]; int belong[maxn]; int bct; int Time; int dfn[maxn]; int low[maxn];
stack<int> s;
vector<int> e[maxn],G[maxn];
int n,m,len,id;

void tarjan(int u,int fa)
{
    dfn[u]=low[u]=++Time;
    s.push(u);
    in[u]=true;
    int sz=e[u].size();
    bool tag=false;
    for(int i=0;i<sz;i++)
    {
        int v=e[u][i];
        if(v==fa&&!tag)
        {
            tag=true;
            continue;
        }
        if(!dfn[v])
        {
            tarjan(v,u);
            low[u]=min(low[u],low[v]);
        }
        else if(in[v]) low[u]=min(low[u],dfn[v]);
    }
    if(low[u]==dfn[u])
    {
        int v;
        ++bct;
        do{
            v=s.top(); s.pop();
            belong[v]=bct;
            in[v]=false;
        }while(v!=u);
    }
}
void tarjan_init()
{
    bct=Time=0;
    for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i,i);
    for(int u=1;u<=n;u++)
    {
        int sz=e[u].size();
        for(int j=0;j<sz;j++)
        {
            int v=e[u][j];
            if(belong[u]==belong[v]) continue;
            G[belong[u]].push_back(belong[v]);
        }
    }
}
void dfs(int u,int fa,int step)
{
    if(step>len) { len=step; id=u; }
    int sz=G[u].size();
    for(int i=0;i<sz;i++)
    {
        int v=G[u][i];
        if(v==fa) continue;
        dfs(v,u,step+1);
    }
}
//扩展GCD
ll ex_gcd(ll a,ll b,ll &x,ll &y){
   if(b==0){x = 1ll;y = 0ll;return a;}
   ll g = ex_gcd(b,a%b,x,y);
   ll temp = x;
   x = y;
   y = temp - a/b*y;
   return g;
}
//扩展欧几里得的另一种写法
void ex_gcd(ll a,ll b,ll &d,ll &x,ll &y){
    //求解ax+by=gcd(a,b)的一组解
    if(!b){
        d=a,x=1ll,y=0ll;
    }
    else{
        ex_gcd(b,a%b,d,y,x);
        y-=x*(a/b);
    }
}


//逆元
ll inv(ll a,int mod){
   ll X,Y;
   ll g = ex_gcd(a,mod,X,Y);
   if(g!=1)return -1;
   return (X%mod + mod)%mod;
}

signed main(void)
{
    scanf("%lld%lld",&n,&m);
    for(int i=1;i<=m;i++)
    {
        int u,v;
        scanf("%lld%lld",&u,&v);
        e[u].push_back(v);
        e[v].push_back(u);
    }
    tarjan_init();
    len=0;
    dfs(1,0,0);
    len=0;
    dfs(id,0,0);
    int ans=inv(m+1,mod)*(bct-1-len)%mod;
    printf("%lld\n",ans);
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值