题意:
- 给定一个带权有向图,一个点集(k个点,无相同点)
- 求点集元素间的最短距离
规模:
- 1<=n,m,k<=1e5
- 1<=w<=1e5
类型:
- 图论,最短路
- 二进制优化
分析:
- 考虑最暴力的做法,枚举点集里所有点,求单源最短路,复杂度k*nlogm,当然是过不了
- 这里我们优化求最短路的次数
纯暴力的做法很容易看出来有多余的计算
全集合最短路有两个端点,分别位于两个点集S,T。
- 如果我们能将集合内任意两点,分割到两个点集里,求最短路,那么一定包含全集合的最短路
- 减少分割次数就能优化掉k
- 任意两点的序号的二进制不同,一定有一位二进制位能将两点划分到两个集合
- 枚举二进制位来划分最多20次
时间复杂度&&优化:
- O(20*nlogm)
代码:
#include <iostream>
#include <bits/stdc++.h>
using namespace std;
const int MAXN=1e5+10;
const int MAXM=1e5+10;
const int INF=0x3fff3fff;
int n,m,k;
int kk[MAXN];
int dist[MAXN];
struct Edge{
int v,w;
};
vector<Edge> edge[MAXN];
void addedge(int u,int v,int w){
Edge x;x.v=v;x.w=w;
edge[u].push_back(x);
}
struct node{
int x;
int dis;
node(int _x=0,int _dis=0):x(_x),dis(_dis){}
bool operator < (const node &other)const{
if(dis!=other.dis)return dis>other.dis;
return x-other.x;
}
};
priority_queue<node> q;
int vis[MAXN];
int dijstra(){
int res;
while(!q.empty()){
node now=q.top();
q.pop();
int u=now.x;
int dis=now.dis;
if(vis[u])return dis;
//vis[u]=1; //多了这行会wa,毕竟不是单源最短路,目标点也是固定的点集
for(int i=0;i<edge[u].size();i++){
int v=edge[u][i].v;
int w=edge[u][i].w;
if(dist[v]>dist[u]+w){
dist[v]=dist[u]+w;
q.push(node(v,dist[v]));
}
}
}
return INF;
}
void init(){
for(int i=0;i<MAXN;i++)dist[i]=INF;
memset(vis,0,sizeof(vis));
while(!q.empty())q.pop();
}
int solve(){
int res=INF;
for(int i=0;i<20;i++){
init();
for(int j=0;j<k;j++){
int v=kk[j];
if(v&(1<<i)){
q.push(node(v,0));
dist[v]=0;
}
else {
vis[v]=1;
}
}
res=min(res,dijstra());
init();
for(int j=0;j<k;j++){
int v=kk[j];
if(!( v&(1<<i) )){
q.push(node(v,0));
dist[v]=0;
}
else {
vis[v]=1;
}
}
res=min(res,dijstra());
}
return res;
}
int main()
{
int T;
cin>>T;
int kase=0;
while(T--){
scanf("%d%d",&n,&m);
for(int i=0;i<MAXN;i++)edge[i].clear();
for(int i=0;i<m;i++){
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
addedge(u,v,w);
}
scanf("%d",&k);
for(int i=0;i<k;i++)scanf("%d",&kk[i]);
printf("Case #%d: %d\n", ++kase,solve());
}
return 0;
}