题目大意:有一个(n)个点,(m)条单向边的图,有(k)个特殊点,求(k)个特殊点间的最短路的最小值。
(nle 10^5,mle 5 imes 10^5)
题目思路:
这个数据大小,用(k)次单源最短路的方法是不可行的。这里需要一个船新的方法:二进制分组。
方法是:每次将编号中某位二进制位不同点的分为两组,一组连源点,一组连汇点,边权均为(0),以源点为起点跑一次最短路,得出源点到汇点的最短路,再反着做一次(因为对于某个点对((x,y))和((y,x))。它们的最短路可能不同)。每一位均操作一次后,得到的结果中的最小值即为答案。这样,我们只需要做约(log_2 k)次最短路,可以通过本题。
我们来证明一下解法的正确性:
对于所有的点对,每两个点必然有至少一位二进制位不同。也就是说,至少有一次分组中,两个点会被分到不同组。
因此,上文解法相当于每个点对都做了次最短路,更准确的说,是一堆点对同时做一次最短路。
上代码
#include<bits/stdc++.h>
#define S n+1
#define T n+2
#define N 100100
#define M 700100
using namespace std;
int cc,to[M],net[M],fr[N],l[M],f[N],h[M],ha[M],p[N];bool vis[N];
int g,n,m,k,t,u,v,len;
void addedge(int u,int v,int len)
{
cc++;
if (u==v) return ;
to[cc]=v;net[cc]=fr[u];fr[u]=cc;l[cc]=len;
}
void add(int x,int xx)
{
g++;
h[g]=x;ha[g]=xx;
int fa=g/2,so=g;
while (h[fa]>h[so]&&fa)
{
swap(h[fa],h[so]);
swap(ha[fa],ha[so]);
so=fa;fa=fa/2;
}
}
int del()
{
int ans=ha[1];
h[1]=h[g];ha[1]=ha[g];g--;
int fa=1,so=2;
if (h[so]>h[so+1]&&so+1<=g) so++;
while (h[fa]>h[so]&&so<=g)
{
swap(h[fa],h[so]);
swap(ha[fa],ha[so]);
fa=so;so*=2;
if (h[so]>h[so+1]&&so+1<=g) so++;
}
return ans;
}
void dij()
{
for (int i=1;i<=n+2;i++) f[i]=2147483647,vis[i]=false;
f[S]=0;add(f[S],S);
while (g)
{
int x=del();
if (vis[x]) continue;
for (int i=fr[x];i;i=net[i])
{
if (f[to[i]]>f[x]+l[i])
{
f[to[i]]=f[x]+l[i];
add(f[to[i]],to[i]);
}
}
}
}
int main()
{
cin>>t;
for (int tt=1;tt<=t;tt++)
{
cin>>n>>m>>k;
cc=0;int ans=2147483647;
for (int i=1;i<=n;i++) fr[i]=0;
for (int i=1;i<=m;i++)
{
cin>>u>>v>>len;
addedge(u,v,len);
}
for (int i=1;i<=k;i++)
cin>>p[i];
for (int i=1;i<=k;i=i<<1)
{
cc=m;
for (int j=1;j<=k;j++)
{
if (i&j) addedge(S,p[j],0);else addedge(p[j],T,0);//二进制分组
}
dij();
ans=min(ans,f[T]);
for (int j=1;j<=k;j++)//复原
{
if (fr[p[j]]>m) fr[p[j]]=net[fr[p[j]]];
}
fr[S]=0;
cc=m;
for (int j=1;j<=k;j++)//反着做一次
{
if (!(i&j)) addedge(S,p[j],0);else addedge(p[j],T,0);
}
dij();
ans=min(ans,f[T]);
for (int j=1;j<=k;j++)
{
if (fr[p[j]]>m) fr[p[j]]=net[fr[p[j]]];
}
fr[S]=0;
}
cout<<ans<<endl;
}
return 0;
}
当然,还有另外的解法。该解法是更优的。似乎锤了std?
考虑边((u,v)),经过这条边的,特殊点的最短路的最小值是离(u)最近特殊点的最短路+((u,v))长度+离(v)最近的特殊点的最短路。
那么,正反做两遍dij(从源点开始)即可。
没写代码