题意
有一张 \(n\) 个点 \(m\) 条边的带权有向图 \((n \le 50000,\ m \le 200000)\),
其中有 \(k\) 条边是一定要经过的 \((k \le 12)\),
从节点 \(1\) 出发, 求 : 经过了所有必须经过的边之后,又回到节点 \(1\) 的最短路径.
思路
首先, 虽然点的数量比较大, 但是大部分是没用的, 我们只需要关注必须经过的那几条边 (称为桥) 的端点.
所以, 一开始先分别以 \(1\) 节点和所有桥的端点作为起点, 跑若干遍 \(dijkstra\), 并用这若干个点之间的最短距离建一张新图.
然后, 考虑状压dp, 设 \(f[u][i]\) 为到了当前点为 \(u\), 经过的边集(桥集)为 \(i\) 时的最短路径.
但由于这是一张无向图, 没有拓扑序, 无法直接dp,
所以, 我们每次枚举到一个边集时, 先更新所有选到的桥的端点的 \(f\) 值, 并把这些点加入优先队列里, 然后跑一边 dijkstra, 把剩下的所有点都更新.
代码
#include<bits/stdc++.h> #define ll long long #define mkp make_pair using namespace std; const int N=5e4+7; const int M=2e5+7; const int K=12+7; const int L=4096+7; int n,m,k,all,poi,num[N],cnt,bdg[2*K][2*K],bi[K],rec[L]; ll f[2*K+1][L],dis[N],mp[2*K+1][2*K+1],ans,inf; int lst[N],nxt[2*M],to[2*M],tot; ll len[2*M]; bool b[N]; struct bridge{ int x,y; ll w; }e[M]; void add(int x,int y,ll w){ nxt[++tot]=lst[x]; to[tot]=y; len[tot]=w; lst[x]=tot; } void read(){ cin>>n>>m>>k; num[1]=++cnt; int x,y; for(int i=1;i<=m;i++){ scanf("%d%d%lld",&x,&y,&e[i].w); if(!num[x]) num[x]=++cnt; if(!num[y]) num[y]=++cnt; x=num[x]; y=num[y]; e[i].x=x; e[i].y=y; add(x,y,e[i].w); add(y,x,e[i].w); if(i<=k){ bdg[x][y]=bdg[y][x]=i; poi=cnt; } } for(int i=1;i<=k;i++){ bi[i]=1<<i-1; rec[bi[i]]=i; } all=(1<<k)-1; memset(f,127,sizeof(f)); memset(mp,-1,sizeof(mp)); inf=f[0][0]; } priority_queue<pair<ll,int> >h; void dijk(int st){ memset(dis,127,sizeof(dis)); memset(b,0,sizeof(b)); dis[st]=0; h.push(mkp(0,st)); while(!h.empty()){ int u=h.top().second; h.pop(); if(b[u]) continue; b[u]=1; for(int i=lst[u];i;i=nxt[i]) if(dis[to[i]]>dis[u]+len[i]){ dis[to[i]]=dis[u]+len[i]; h.push(mkp(-dis[to[i]],to[i])); } } for(int i=1;i<=2*k+1;i++) if(dis[i]!=inf) mp[st][i]=dis[i]; } void print(int x){ for(int i=1;i<=k;i++){ printf("%d",x&1); x>>=1; } } void run(int i){ while(!h.empty()){ int u=h.top().second; h.pop(); if(b[u]) continue; b[u]=1; for(int v=1;v<=n;v++) if(mp[u][v]!=-1&&f[v][i]>f[u][i]+mp[u][v]){ f[v][i]=f[u][i]+mp[u][v]; h.push(mkp(-f[v][i],v)); } } } int main(){ // freopen("bridge.in","r",stdin); // freopen("bridge.out","w",stdout); read(); for(int i=1;i<=2*k+1;i++) dijk(i); f[1][0]=0; h.push(mkp(0,1)); n=poi; ans=inf; for(int i=0;i<=all;i++){ for(int j=i;j;j-=j&-j){ int t=rec[j&-j]; int x=e[t].x,y=e[t].y; f[x][i]=min(f[x][i],f[y][i^bi[t]]+e[t].w); f[y][i]=min(f[y][i],f[x][i^bi[t]]+e[t].w); h.push(mkp(-f[x][i],x)); h.push(mkp(-f[y][i],y)); } memset(b,0,sizeof(b)); run(i); } for(int i=1;i<=n;i++) if(f[i][all]!=inf) ans=min(ans,f[i][all]+mp[i][1]); printf("%lld\n",ans); return 0; }