模拟赛 最小环 题解
求包含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;
}