题意:
有一个n个点m条边的带权有向图,现在标记k个点,问这k个点中最近的点的距离
数据范围:n,m<=1e5
解法:
暴力做法显然不行
正解比较巧妙:
考虑一个问题:假设有两个点集A和B,求A中的点到B中的点的最近距离
超级源点连接A中的点,B中的点连接超级汇点
那么答案就是超级源点到超级汇点的最短距离
这题也可以利用这个思路,将k个标记的点分成两个点集A和B
然后计算A到B的最短距离,这样就可以求出部分解,可以进行多次花费来补全解
但是部分解显然不够全面,需要保证每个点对都存在一次划分使得i和j分别处于不同的集合
这样才能保证是完备解
那么如何划分呢?
有一个巧妙的思路是二进制划分,因为每个点的二进制编码是不是一样的
因此按照二进制的每一位01划分,对于每个点对一定都有某次划分在两个不同的集合
因为数据范围1e5,因此只需要花费17次就够了
注意是有向边,因此每次划分要跑两个方向共两次最短路
code:
#include<bits/stdc++.h>
using namespace std;
const int maxm=1e5+5;
int head[maxm],nt[maxm],to[maxm],w[maxm],cnt;
int stk[maxm],top;
int dist[maxm];
int mark[maxm];
int a[maxm];
int n,m,k;
void add(int x,int y,int z){
cnt++;nt[cnt]=head[x];head[x]=cnt;to[cnt]=y;w[cnt]=z;
}
typedef pair<int,int> PI;
int dj(int bit,int flag){
for(int i=1;i<=n;i++)dist[i]=1e9,mark[i]=0;
priority_queue<PI,vector<PI>,greater<PI> >q;
top=0;
for(int i=1;i<=k;i++){
if((a[i]>>bit&1)==flag){//源点连接的点
q.push({0,a[i]});//直接入队,不用建立源点
dist[a[i]]=0;
}else{//汇点连接的点
stk[++top]=a[i];//存起来最后遍历取min就行了,不用建立汇点
}
}
while(!q.empty()){
int x=q.top().second;q.pop();
if(mark[x])continue;
mark[x]=1;
for(int i=head[x];i;i=nt[i]){
int v=to[i];
if(!mark[v]&&dist[v]>dist[x]+w[i]){
dist[v]=dist[x]+w[i];
q.push({dist[v],v});
}
}
}
int mi=1e9;
for(int i=1;i<=top;i++){
mi=min(mi,dist[stk[i]]);
}
return mi;
}
signed main(){
int T;scanf("%d",&T);
int cas=1;
while(T--){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)head[i]=0;
cnt=0;//!!!
for(int i=1;i<=m;i++){
int a,b,c;scanf("%d%d%d",&a,&b,&c);
add(a,b,c);
}
scanf("%d",&k);
for(int i=1;i<=k;i++)scanf("%d",&a[i]);
int ans=1e9;
for(int i=0;i<17;i++){
ans=min(ans,dj(i,0));
ans=min(ans,dj(i,1));
}
printf("Case #%d: %d\n",cas++,ans);
}
return 0;
}