P5304-[GXOIGZOI2019]旅行者【最短路】

    科技2026-09-30  12

    正题

    题目链接:https://www.luogu.com.cn/problem/P5304


    题目大意

    n n n个点 m m m条边的一张有向图,有 k k k个关键点,求距离最短的一对关键点。


    解题思路

    我们能够 O ( n log ⁡ n ) O(n\log n) O(nlogn)的进行一个起点或多个起点同时的最短路,但是这题显然不能枚举其中的一对点。

    可以考虑多个起点的最短路做法,我们可以做到每次计算一组点到另一组点之间的最短路,考虑如何分组能够让每一队都分到过不同的组。

    我们可以枚举一个位数 i i i,对于第 x x x个关键点,如果 x x x的第 i i i位为 1 1 1那么久分入第一个点组,否则分入第二个点组,那么这样分组就可以做到每个点对都分到过不同的组。

    时间复杂度 O ( n log ⁡ 2 n ) O(n\log^2 n) O(nlog2n)


    c o d e code code

    #include<cstdio> #include<cstring> #include<algorithm> #include<queue> #define ll long long using namespace std; const ll N=1e5+10; struct edge_node{ ll x,y,w; }e[N*5]; struct node{ ll to,next,w; }a[N*7]; struct point{ ll pos,dis; bool operator<(const point &x)const {return x.dis<dis;} }; priority_queue<point> q; ll T,n,m,k,tot,ans,s,t; ll z[N],ls[N],f[N]; bool v[N]; void addl(ll x,ll y,ll w){ a[++tot].to=y; a[tot].next=ls[x]; a[tot].w=w; ls[x]=tot; } void Dij(){ memset(f,0x3f,sizeof(f)); memset(v,0,sizeof(v)); q.push((point){s,0});f[s]=0; while(!q.empty()){ ll x=q.top().pos,w=q.top().dis; q.pop();if(v[x])continue;v[x]=1; for(ll i=ls[x];i;i=a[i].next){ ll y=a[i].to; if(f[x]+a[i].w<f[y]){ f[y]=f[x]+a[i].w; if(!v[y])q.push((point){y,f[y]}); } } } return; } int main() { scanf("%lld",&T); while(T--){ scanf("%lld%lld%lld",&n,&m,&k); s=0;t=n+1;ans=1e18; for(ll i=1;i<=m;i++) scanf("%lld%lld%lld",&e[i].x,&e[i].y,&e[i].w); for(ll i=1;i<=k;i++)scanf("%lld",&z[i]); for(ll p=0;(1<<p)<=k;p++){ tot=0;memset(ls,0,sizeof(ls)); for(ll i=1;i<=m;i++)addl(e[i].x,e[i].y,e[i].w); for(ll i=1;i<=k;i++) if((i>>p)&1)addl(s,z[i],0); else addl(z[i],t,0); Dij(); ans=min(ans,f[t]); tot=0;memset(ls,0,sizeof(ls)); for(ll i=1;i<=m;i++)addl(e[i].x,e[i].y,e[i].w); for(ll i=1;i<=k;i++) if((i>>p)&1)addl(z[i],t,0); else addl(s,z[i],0); Dij(); ans=min(ans,f[t]); } printf("%lld\n",ans); } }
    Processed: 0.009, SQL: 9