2017.10.14 T3 1982
样例数据
输入
1
5 6
1 2 1
2 3 3
3 1 3
2 5 1
2 4 2
4 3 1
3
1 3 5
输出
2
分析:这是一道很妙的题(考试的时候只会40%的暴力)。
100%:把可以选的点分成两部分,一部分当起点,一部分当终点,转化成多起点多终点的最短路,建立一个对起点点集建立超级起点,跑一次得到这种分法之间的最短路。
但这也许并不是正解,因为也许最短的一条路径的端点都在起点集合中或者终点集合中。
所以要想办法把这些点多次分组把每种情况都能考虑到。考虑二进制分解,因为对于每两个不等的数,至少有一个二进制位不一样,这样我们可以枚举每一个二进制位,然后按0、1分成两组,就可以保证每两个点都分到过不同的集合中,然后跑最短路,答案肯定不会有遗漏,而且只需要分
logk
次。
代码
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<ctime>
#include<cmath>
#include<algorithm>
#include<cctype>
#include<iomanip>
#include<queue>
#include<set>
using namespace std;
int getint()
{
int sum=0,f=1;
char ch;
for(ch=getchar();!isdigit(ch)&&ch!='-';ch=getchar());
if(ch=='-')
{
f=-1;
ch=getchar();
}
for(;isdigit(ch);ch=getchar())
sum=(sum<<3)+(sum<<1)+ch-48;
return sum*f;
}
const int N=100010;
int first[N],to[N*2],nxt[N*2],w[N*2],tot;
int T,n,m,x,y,z,k,sp[N],num[N],dis[N],ans;
priority_queue<pair<int,int> > que;
void addedge(int x,int y,int z)
{
tot++;
nxt[tot]=first[x];
first[x]=tot;
to[tot]=y;
w[tot]=z;
tot++;
nxt[tot]=first[y];
first[y]=tot;
to[tot]=x;
w[tot]=z;
}
void getans()
{
for(int t=0;(1<<t)<=k;++t)//枚举二进制位
{
memset(dis,0x3f3f3f3f,sizeof(dis));
while(!que.empty()) que.pop();//因为dijkstra里有break,所以必须把que清空
for(int i=1;i<=k;++i)
if(num[sp[i]]&(1<<t))//按二进制位分组
{
dis[sp[i]]=0;//其实也没有超级起点,只是把所有的起点组里的dis都赋成0加入队列
que.push(make_pair(0,sp[i]));
}
while(!que.empty())//dijkstra+优先队列
{
int u=que.top().second;
que.pop();
if(num[u]&&!(num[u]&(1<<t)))//最先在优先队列中找到的终点组里的点一定是和某个起点组的点距离最短的点,后面的dis都比它大,所以就不用继续了,更新一下ans继续分组
{
ans=min(ans,dis[u]);
break;
}
for(int p=first[u];p;p=nxt[p])
{
int v=to[p];
if(dis[v]>dis[u]+w[p])
{
dis[v]=dis[u]+w[p];
que.push(make_pair(-dis[v],v));
}
}
}
}
}
int main()
{
freopen("path.in","r",stdin);
freopen("path.out","w",stdout);
T=getint();
while(T--)
{
tot=0,ans=0x3f3f3f3f;//清零操作
memset(first,0,sizeof(first));
memset(num,0,sizeof(num));
n=getint(),m=getint();
for(int i=1;i<=m;++i)
{
x=getint(),y=getint(),z=getint();
addedge(x,y,z);
}
k=getint();
for(int i=1;i<=k;++i)
sp[i]=getint(),num[sp[i]]=i;//记录每一个可以选的点并按读取顺序给他们一个编号num
getans();
cout<<ans<<'\n';
}
return 0;
}
本题结。