模拟赛 最小环 题解

求包含1号点的最小环。

这个最小环一定是从1点的出边指向的点出发,再回到另一个1点的出边指向的点。

这等价于1号点所有出边指向的点中,两两之间最短路+1号点到这两个点的距离的最小值。

使用二进制拆分,分成两组点,分别向s,t连边,正反算两次最短路。

时间复杂度 \(O((n+m)log^2 n)\)

二进制分组是很常用的思路。

参考代码:

#include <stdio.h>
#include <queue>
using namespace std;
int fr[5010],ne[30010],inf=999999999;
int v[30010],w[30010],bs=0,n;
int jl[5010],sz[5010],cd1[5010],cd2[5010];
bool bk[5010];
void addb(int a,int b,int c)
{
    v[bs]=b;
    w[bs]=c;
    ne[bs]=fr[a];
    fr[a]=bs;
    bs+=1;
}
struct SJd               
{                        
    int u,z;
    SJd(){};
    SJd(int Z,int U)
    {
        z=Z;
        u=U;
    }
    bool operator<(const SJd&a)const
    {
        return z>a.z;
    }
};
int dij(int S,int T)
{
    for(int i=1;i<=n;i++)
    {
        bk[i]=false;
        jl[i]=inf;
    }
    jl[S]=0;
    priority_queue<SJd> dl;
    dl.push(SJd(0,S));
    while(!(dl.empty()))
    {
        SJd t=dl.top();
        dl.pop();
        if(bk[t.u])
            continue;
        bk[t.u]=true;
        for(int j=fr[t.u];j!=-1;j=ne[j])                  
        {      
            if(v[j]==1)
                continue;
            if(t.z+w[j]<jl[v[j]])                        
            { 
                jl[v[j]]=t.z+w[j];
                dl.push(SJd(jl[v[j]],v[j]));
            }
        }                                                
    }
    return jl[T];
}
int main()
{
    freopen("B.in","r",stdin);
    freopen("B.out","w",stdout);
    int m,k=0,jg=inf;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n+2;i++)
        fr[i]=-1;
    for(int i=0;i<m;i++)
    {
        int a,b,c,d;
        scanf("%d%d%d%d",&a,&b,&c,&d);
        addb(a,b,c);
        addb(b,a,d);
    }
    for(int i=fr[1];i!=-1;i=ne[i])
    {
        sz[k]=v[i];
        cd1[k]=w[i];
        cd2[k]=w[i^1];
        k+=1;
    }
    n+=2;
    for(int i=16;i>=0;i--)
    {
        for(int j=0;j<k;j++)
        {
            if(j&(1<<i))
            {
                addb(n,sz[j],cd1[j]);
                addb(sz[j],n,cd1[j]);
            }
            else
            {
                addb(n-1,sz[j],cd2[j]);
                addb(sz[j],n-1,cd2[j]);
            }
        }
        int rt=dij(n,n-1);
        if(rt<jg)
            jg=rt;
        bs-=k*2;
        for(int j=0;j<k;j++)
            fr[sz[j]]=ne[fr[sz[j]]];
        fr[n]=fr[n-1]=-1;
        //
        for(int j=0;j<k;j++)
        {
            if(!(j&(1<<i)))
            {
                addb(n,sz[j],cd1[j]);
                addb(sz[j],n,cd1[j]);
            }
            else
            {
                addb(n-1,sz[j],cd2[j]);
                addb(sz[j],n-1,cd2[j]);
            }
        }
        rt=dij(n,n-1);
        if(rt<jg)
            jg=rt;
        bs-=k*2;
        for(int j=0;j<k;j++)
            fr[sz[j]]=ne[fr[sz[j]]];
        fr[n]=fr[n-1]=-1;
    }
    printf("%d",jg);
    fclose(stdin);
    fclose(stdout);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值