题意
- 给你n个点,从其中选出一些点可以构成一个点集合,给你m个点集合,在一个集合里的点相互到达的时间是d
- 问你从1号点和n号点分别出发一个人,选在哪些点相遇,可以使总的相遇时间最短。
- n是2~100000,每个点集合里的点个数和小于10^6
思路
- 朴素的想法,就是对求1和n到各个点的最短路,然后选取对于每个点计算max(dis(1),dis(n)),再选取其中最小值的点。
- 但由于之间建图,会出现一个集合有所有点,那么就变成完全图了,10^10条边,肯定不行。
- 我这里的思路是,对每个集合求最短路,每次从优先队列拿出的集合里的点就是找到了最短路,再用这些点去扩展集合,那些点所在的集合里,没有找到最短路的,再继续把它们中更新最短路的加入优先队列中。
- 这个思路,需要改变算法的实现,不过改动不大。
- 后来我看大神的解题报告,他们基本上是重新建图了,使得边数不会太多,然后正常跑最短路模板就好了。。
实现
#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#include <vector>
#include <algorithm>
using namespace std;
int T,n,m;
const int maxn = 100005;
const int maxm = 1000006;
typedef long long ll;
const int inf = 0x3f3f3f3f;
#define pb push_back
struct Set{
int d;
vector<int> v;
Set(int d=0,int s=0):d(d),v(s){
}
~Set(){
v.clear();
}
};
vector<Set> vec;
vector<int> g[maxn];
struct Node{
int id;
ll d;
Node(int id=0,ll d=0):id(id),d(d){
}
bool operator<(const Node& rhs) const{
return d > rhs.d;
}
};
priority_queue<Node> q;
ll d1[maxn];
ll dn[maxn];
int done[maxn];
int doneS[maxm];
ll sd[maxm];
void dks(int s,ll d[]){
for (int i=1;i<=n;i++){
d[i] = inf;
}
for (int i=0;i<m;i++){
sd[i] = inf;
}
memset(done,0,sizeof(done));
memset(doneS,0,sizeof(doneS));
d[s] = 0;
done[s] = 1;
for (int i=0;i<g[s].size();i++){
int e = g[s][i];
sd[e] = vec[e].d;
q.push(Node(e,sd[e]));
}
while (q.size() > 0){
Node tmp = q.top();
q.pop();
int id = tmp.id;
if (doneS[id] == 1){
continue;
}
doneS[id] = 1;
ll dis = (ll)vec[id].d;
for (int i=0;i<vec[id].v.size();i++){
int v = vec[id].v[i];
if (done[v]){
continue;
}
done[v] = 1;
d[v] = tmp.d;
for (int j=0;j<g[v].size();j++){
int e = g[v][j];
if (doneS[e] == 1){
continue;
}
if (d[v] + (ll)vec[e].d < sd[e]){
sd[e] = d[v] + (ll)vec[e].d;
q.push(Node(e,sd[e]));
}
}
}
}
}
int main(){
cin>>T;
for (int t=1;t<=T;t++){
vec.clear();
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++)
g[i].clear();
for (int i=0;i<m;i++){
int d,s;
scanf("%d%d",&d,&s);
vec.pb(Set(d,s));
for (int j=0;j<s;j++){
scanf("%d",&vec[i].v[j]);
g[vec[i].v[j]].pb(i);
}
}
dks(1,d1);
dks(n,dn);
for (int i=1;i<=n;i++){
ans = min(ans,max(d1[i],dn[i]));
}
if (ans == inf)
printf("Case #%d: Evil John\n",t);
else{
printf("Case #%d: %lld\n",t,ans);
int flag = 0;
for (int i=1;i<=n;i++){
if (max(d1[i],dn[i]) == ans){
if (flag == 0){
printf("%d",i);
flag = 1;
}
else{
printf(" %d",i);
}
}
}
puts("");
}
}
return 0;
}