/*
最短路, 终点集合到s的最远距离最短,求s. 即已知终点集{d}求一s使得Min{ max{ dis(s, di) } }
好题
思路: 多次单源最短路,选出最大值
在对每个x进行分层搜索的过程中, 用max_d[y]记录每个地区x到达地区y的最短距离中的最大值. 最后求得的Star Value就是max_d[]中的最小值.
由于题目的特殊性`边权都为1`,所以可以借助这一性质变换一下SPFA使其更快。
说个题外话,在临高时看到有个学弟拓扑排序用到“分层思想”,一直觉得很妙。就是拓扑后我们可以得到floor[i],如果floor[i] > floor[j],即说明j是i的前驱节点(层数越小越接近root); 而floor[i] == floor[j]的话则i,j的相对顺序无所谓,因为他们都在“同一层”。
这里因为边权都为1,所以SPFA可以用到上述的分层思想,层数越高,离source越远。代码里面floors就表示层数,Q是滚动队列,就是一层一层地relax后继节点。
注意!!千万不要以为max_d[]是最短路算法里面的dis[],这里的max_d[i]是到点i到终点集合{di}的最大值!而常规最短路算法里的dis[]已经被省略为“层数”了,不需要记录,所以没开数组。
最重要的是学到一个tip!!以前我做多次最短路的时候总要每次都初始化visit[] -> false,但其实不用的,我们只要用一个变量when表示“这是第几次做SPFA(或其他)“,然后每次入队前都看”是否当前visit[v] == when就可以直到改点是否已经入过队......
*/
#include <stdio.h>
#include <string.h>
#include <vector>
#include <deque>
using namespace std;
#define debug printf("!\n")
#define INF 999999999
#define MAXN 10000
int n;
int max_d[MAXN];
int visit[MAXN];
vector<int> v[MAXN];
void SPFA(int s, int when);
void init();
int main()
{
int cases, query, id, m, y, x;
scanf("%d", &cases);
while(cases--) {
scanf("%d%d", &n, &query);
init();
for(int i = 0; i < n; i++) {
scanf("%d%d", &id, &m);
while(m--) {
scanf("%d", &y);
v[id].push_back(y);
}
}
int when = 0;
while(query--) {
scanf("%d", &m);
while(m--) {
scanf("%d", &x);
SPFA(x, ++when);
}
}
int ans = INF, ans_id = -1;
for(int i = 1; i < MAXN; i++) if(!v[i].empty() && max_d[i] < ans) ans = max_d[i], ans_id = i;
printf("%d %d\n", ans, ans_id);
}
return 0;
}
void init()
{
for(int i = 0; i < MAXN; i++) v[i].clear();
memset(max_d, 0, sizeof(max_d));
memset(visit, 0, sizeof(visit));
}
void SPFA(int s, int when)
{
deque<int> Q[2];
int cur = 0;
Q[cur].push_back(s);
max_d[s] = max(max_d[s], 1);
visit[s] = when;
int floors = 1;
do {
floors++;
while(!Q[cur].empty()) {
int at = Q[cur].front();
Q[cur].pop_front();
for(int Size = v[at].size(), i = 0; i < Size; i++) {
int to = v[at][i];
if(visit[to] != when) { //是否已入队
//max_d[to] = max(max_d[to], max_d[at]+1); 这句是不对的,因为这个分层跟拓扑排序的分层是不一样的,拓扑排序是要在入度为0时才能加进队Q,所以可以这样写,但是这里只要第一次遇见点to就必须得入队,因为要的是最短路径
max_d[to] = max(max_d[to], floors); //不把这句放在if外面,因为这里的max_d[to]是距离s的最短路径,最短路径也就是最小层数,最小层数在to第一次入队的时候已经得到了
visit[to] = when;
Q[1-cur].push_back(to);
}
}
}
cur = 1 - cur;
} while(!Q[cur].empty());
}