题目:点击打开链接
题意:给一个有向图,n个点,m条边。给k个其中的点,求这k个点相互之间的路径值的最小值是多少。
思路:如果是暴力枚举这k个点到其他k-1个点的最短路取最小值一定是超时的。
正确做法是每次将k个点划分成两个集合,求这两个集合的最短路,取最小值。划分的方法应该保证这k个点均相互求过最短路,且划分次数不应太多以免超时。
这道题最难想到的是对这k个点进行划分,这里方法是这样的:
将这k个点用二进制来划分。首先两个不相同的数,其二进制一定有一位是不同的。根据这种特性,将某一位上为0的分到一个集合,为1的分到另外一个集合,求一次多源最短路。那么100000就是2^17就够了,也就是17次最短路。
代码:
#include <bits/stdc++.h>
using namespace std;
const int INF=0x3f3f3f3f;
const int MAXN=100010;
struct qnode
{
int v;
int c;
qnode(int _v=0,int _c=0):v(_v),c(_c) {}
bool operator <(const qnode &r)const
{
return c>r.c;
}
};
struct Edge
{
int v,cost;
Edge(int _v=0,int _cost=0):v(_v),cost(_cost) {}
};
vector<Edge>E[MAXN];
bool vis[MAXN];
int dist[MAXN];
priority_queue<qnode>que;
void Dijkstra()//点的编号从1开始
{
qnode tmp;
while(!que.empty())
{
tmp=que.top();
que.pop();
int u=tmp.v;
if(vis[u]) continue;
vis[u]=true;
for(int i=0; i<E[u].size(); i++)
{
int v=E[tmp.v][i].v;
int cost=E[u][i].cost;
if(!vis[v]&&dist[v]>dist[u]+cost)
{
dist[v]=dist[u]+cost;
que.push(qnode(v,dist[v]));
}
}
}
}
void addedge(int u,int v,int w)
{
E[u].push_back(Edge(v,w));
}
int n,a[MAXN],k,ans;
void solve()
{
for(int i=1; i<=(1<<17); i<<=1)
{
memset(vis,false,sizeof(vis));
for(int j=1; j<=n; j++)dist[j]=INF;
while(!que.empty())que.pop();
for(int j=1; j<=k; j++)
if(a[j]&i)
{
dist[a[j]]=0;
que.push(qnode(a[j],0));
}
Dijkstra();
for(int j=1; j<=k; j++)
if(!(a[j]&i))
ans=min(ans,dist[a[j]]);
memset(vis,false,sizeof(vis));
for(int j=1; j<=n; j++)dist[j]=INF;
while(!que.empty())que.pop();
for(int j=1; j<=k; j++)
if(!(a[j]&i))
{
dist[a[j]]=0;
que.push(qnode(a[j],0));
}
Dijkstra();
for(int j=1; j<=k; j++)
if(a[j]&i)
ans=min(ans,dist[a[j]]);
}
}
int main()
{
int T,m,cas=1;
scanf("%d",&T);
while(T--)
{
scanf("%d%d",&n,&m);
for(int i=1; i<=n; i++)E[i].clear();
for(int i=1; i<=m; i++)
{
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
addedge(u,v,w);
}
scanf("%d",&k);
for(int i=1; i<=k; i++)
scanf("%d",&a[i]);
ans=INF;
solve();
printf("Case #%d: %d\n",cas++,ans);
}
return 0;
}