听ShallWe讲了之后觉得非常厉害呀。
首先,在每两个有边的点中间都加一个点,每条边的边权都是1,然后将s、t、所有的加油站都加到一个队列里,做广搜。广搜的时候每次都将边的始点和终点用并查集并起来,直到s和t连通,当前搜到的点的dis就是答案。
考虑一下这样为什么是对的。基于广搜的性质搜到的每一个点的dis一定是到最近的加油站的距离,而同时当s和t恰好连通时当前搜到的dis一定是这些里面的最大值。这样就非常巧妙地保证了正确性。
同时拆点的思路也非常厉害,可以形象化地理解为两个点碰到了中间。
#include
#include
#include
#include
using namespace std;
#define N 500005
#define M 150005
int T,n,m,k,x,y,z,s,t,ans;
int tot,point[N],nxt[M*4],v[M*4];
int loc[N],dis[N],f[N];
bool vis[N];
queue <int> q;
inline void clear()
{
n=m=k=x=y=z=s=t=ans=tot=0;
memset(point,0,sizeof(point)); memset(nxt,0,sizeof(nxt)); memset(v,0,sizeof(v));
memset(loc,0,sizeof(loc)); memset(dis,0,sizeof(dis)); memset(f,0,sizeof(f)); memset(vis,0,sizeof(vis));
while (!q.empty()) q.pop();
}
inline void addedge(int x,int y)
{
++tot; nxt[tot]=point[x]; point[x]=tot; v[tot]=y;
++tot; nxt[tot]=point[y]; point[y]=tot; v[tot]=x;
}
inline int find(int x)
{
if (x==f[x]) return x;
f[x]=find(f[x]);
return f[x];
}
inline void merge(int x,int y)
{
int f1=find(x),f2=find(y);
f[f1]=f2;
}
inline int bfs()
{
while (!q.empty())
{
int now=q.front(); q.pop();
for (int i=point[now];i;i=nxt[i])
{
merge(now,v[i]);
if (find(s)==find(t))
return dis[v[i]];
if (!vis[v[i]])
{
dis[v[i]]=dis[now]+1;
vis[v[i]]=true;
q.push(v[i]);
}
}
}
return -1;
}
int main()
{
freopen("f1.in","r",stdin);
freopen("f1.out","w",stdout);
scanf("%d",&T);
while (T--)
{
clear();
scanf("%d%d%d",&n,&m,&k);
z=n;
for (int i=1;i<=k;++i) scanf("%d",&loc[i]);
for (int i=1;i<=m;++i)
{
scanf("%d%d",&x,&y);
z++;
addedge(x,z); addedge(y,z);
}
scanf("%d%d",&s,&t);
for (int i=1;i<=z;++i) f[i]=i;
if (!vis[s]) vis[s]=true,q.push(s);
if (!vis[t]) vis[t]=true,q.push(t);
for (int i=1;i<=k;++i)
if (!vis[loc[i]])
{
vis[loc[i]]=true;
q.push(loc[i]);
}
ans=bfs();
printf("%d\n",ans);
}
}
这道题的拆点非常厉害呀。