题目地址:http://acm.hdu.edu.cn/showproblem.php?pid=6166
思路:
1.从集合A中一点到集合B中一点的最短路径:设立源点0,连向所有A集合中的点,边权值为0;设立汇点,B集合中点向其连边,边权值为0。求源点到汇点的最短路即为集合A到集合B的最短路。
2.将最短两点分别分到A、B集合:由于图中任意两点标号不同,其二进制表示中必有至少一位不同。按二进制枚举第0--->17位,若u与v该位不同,则u分到集合A,v分到集合B,求A到B最短路和B到A最短路取最小。
3.由于不同u与v必定存在至少一位不同,所以每个u与每个v都两两分到不同集合(即包含所有组合)其中必有至少一次u与v分到正确集合,此时答案即为最小。
#include<queue>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define debug
using namespace std;
const int maxn=1e5+50;
const int INF=0x3f3f3f3f;
struct Edge
{
int to,nt,w;
};
int n,m,tot;
int a[maxn];
queue<int> q;
int x[maxn],y[maxn],w[maxn];
int head[maxn*10],flag[maxn];
int vis[maxn],dist[maxn];
Edge edge[maxn*10];
void init()
{
tot=0;
memset(head,-1,sizeof(head));
}
void addEdge(int u,int v,int w)
{
edge[tot].to=v,edge[tot].nt=head[u];
edge[tot].w=w,head[u]=tot++;
}
int mindist(int u)
{
memset(vis,0,sizeof(vis));
while(!q.empty()) q.pop();
for(int i=0; i<=n+1; i++) dist[i]=INF;
dist[u]=0,q.push(u),vis[u]=1;
while(!q.empty())
{
int now=q.front();
q.pop(),vis[now]=0;
for(int i=head[now]; ~i; i=edge[i].nt)
{
int nt=edge[i].to;
int w=edge[i].w;
if(dist[nt]>dist[now]+w)
{
dist[nt]=dist[now]+w;
if(!vis[nt])
{
vis[nt]=1;
q.push(nt);
}
}
}
}
return dist[n+1];
}
void build()
{
init();
for(int j=0; j<m; j++)
{
addEdge(x[j],y[j],w[j]);
}
}
int main()
{
#ifdef debu
freopen("in.txt","r",stdin);
#endif // debug
int t,cas=0;
scanf("%d",&t);
while(t--)
{
scanf("%d%d",&n,&m);
for(int i=0; i<m; i++)
{
scanf("%d%d%d",&x[i],&y[i],&w[i]);
}
int k;
scanf("%d",&k);
for(int i=0; i<k; i++)
{
scanf("%d",&a[i]);
}
int ans=INF;
for(int i=0; i<18; i++)
{
memset(flag,0,sizeof(flag));
build();
for(int j=0; j<k; j++)
{
if(a[j]&(1<<i))
{
addEdge(0,a[j],0);
flag[a[j]]=1;
}
else
{
addEdge(a[j],n+1,0);
}
}
//cout<<"& "<<i<<" "<<mindist(0)<<endl;
ans=min(ans,mindist(0));
build();
for(int j=0; j<k; j++)
{
if(flag[a[j]])
{
addEdge(a[j],n+1,0);
}
else
{
addEdge(0,a[j],0);
}
}
//cout<<"* "<<i<<" "<<mindist(0)<<endl;
ans=min(ans,mindist(0));
}
printf("Case #%d: %d\n",++cas,ans);
}
return 0;
}