其实这就是一道搜索题,我们利用优先队列来进行广搜,首先取出没有用过的且离起点最距离最短的点,枚举其所在的团,对于团中的点,能更新则更新,更新后距离若更短则入队。当然了,我们还可以从中发现一个性质,就是在搜索过程中,每个团只用一遍即可,用两遍不会产生比用一遍更好的解,所以就多加一个vis1数组来剪枝即可。
从起点搜一次从终点搜一次,之后从起点枚举到终点记录答案输出即可
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#define N 1000010
typedef long long ll;
using namespace std;
vector<int>v[N];
vector<int>num[N];
ll t[N];
bool vis[N],vis1[N];
ll dis[2][N];
ll n,m;
struct NODE
{
ll x,val;
friend bool operator< (NODE n1,NODE n2)
{
return n1.val>n2.val;
}
};
void bfs(ll s,ll f)
{
ll i,len,tmp,j;
NODE now,next;
priority_queue<NODE>q;
memset(vis1,0,sizeof(vis1));
memset(vis,0,sizeof(vis));
for(i=1;i<=n;i++)
dis[f][i]=1e18;
dis[f][s]=0;
now.val=0;now.x=s;
q.push(now);
while(!q.empty())
{
now=q.top();
q.pop();
if(vis[now.x])
continue;
vis[now.x]=1;
len=num[now.x].size();
for(i=0;i<len;i++)
{
if(!vis1[num[now.x][i]])
{
tmp=num[now.x][i];
ll len_tmp;
len_tmp=v[tmp].size();
for(j=0;j<len_tmp;j++)
{
next.x=v[tmp][j];
if(dis[f][next.x]>dis[f][now.x]+t[tmp])
{
dis[f][next.x]=dis[f][now.x]+t[tmp];
next.val=dis[f][next.x];
q.push(next);
}
}
vis1[tmp]=1;
}
}
}
}
ll ans[N],ans_len,ans_tmp;
int main()
{
ll T;
ll len,i,tmp;
// freopen("in.txt","r",stdin);
int i1=1;
scanf("%lld",&T);
while(T--)
{
scanf("%lld%lld",&n,&m);
for(i=1;i<=n;i++)
num[i].clear();
for(i=1;i<=m;i++)
{
v[i].clear();
scanf("%lld",&t[i]);
scanf("%lld",&len);
while(len--)
{
scanf("%lld",&tmp);
v[i].push_back(tmp);
num[tmp].push_back(i);
}
}
bfs(1,0);
bfs(n,1);
ans_tmp=1e18;
ans_len=0;
for(i=1;i<=n;i++)
{
if(max(dis[0][i],dis[1][i])<ans_tmp)
{
ans_tmp=max(dis[0][i],dis[1][i]);
ans_len=0;
ans[ans_len++]=i;
}
else if(max(dis[0][i],dis[1][i])==ans_tmp)
{
ans[ans_len++]=i;
}
}
printf("Case #%d: ",i1++);
if(ans_len==0||ans_tmp==1e18)
{
printf("Evil John\n");
}
else
{
printf("%lld\n",ans_tmp);
for(i=0;i<ans_len;i++)
{
printf("%lld",ans[i]);
if(i==ans_len-1)
printf("\n");
else
printf(" ");
}
}
}
return 0;
}