#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <vector>
#include <queue>
#include <stack>
#include <map>
#include <string>
#include <set>
using namespace std;
typedef long long ll;
const double EPS = 1e-8;
const int maxn = 1e2+10;
struct node{
int to, next;
}edge[maxn*maxn*2];
int cnt, head[maxn];
int idx;//dfs计数
int dfn[maxn]; //表示dfs遍历到该节点的序号,也就是顺序值
int low[maxn]; //表示当前顶点不通过父亲节点能访问到的祖先节点(父亲节点上面的节点)中的最小顺序值
bool cut[maxn];//是否为割点
void addEdge(int u, int v)
{
edge[cnt].to = v;
edge[cnt].next = head[u];
head[u] = cnt++;
}
//以u为根节点dfs过程中产生一棵搜索树
void Tarjan(int u, int pre)
{
dfn[u] = low[u] = ++idx;
int son = 0;
for(int i = head[u]; i; i = edge[i].next)
{
int v = edge[i].to;
if(v == pre) continue;
if(!dfn[v])
{
son++;
Tarjan(v, u);
low[u] = min(low[u] , low[v]);
//如果u不是树根,且存在儿子v使得dfn(u)<=low(v) 也就是说儿子v无法绕过父亲u到达比父亲时间戳小的点
if(u != pre && low[v] >= dfn[u]) cut[u] = true;
//if(low[v] > dfn[u] ) (u,v)边是桥
}
else
low[u] = min(low[u], dfn[v]);
}
//如果u是树根,并且u不止一个子树
if(u == pre && son > 1) cut[u] = true;
}
void init()
{
cnt = 1;
memset(head, 0, sizeof(head));
idx = 0;
memset(dfn, 0, sizeof(dfn));
memset(low, 0, sizeof(low));
memset(cut, false, sizeof(cut));
}
int main()
{
int n;
while(scanf("%d", &n) && n)
{
init();
int u, v;
while(scanf("%d", &u) && u)
{
while(getchar()!='\n')
{
scanf("%d", &v);
addEdge(u, v);
addEdge(v, u);
}
}
Tarjan(1, 1);
int ans = 0;
for(int i = 1; i<=n; i++)
if(cut[i]) ans++;
printf("%d\n", ans);
}
}
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <vector>
#include <queue>
#include <stack>
#include <map>
#include <string>
using namespace std;
typedef long long ll;
const double EPS = 1e-8;
const int maxn = 1e2+10;
int ans;
struct node{
int to, next;
}edge[maxn*maxn*2];
int cnt, head[maxn];
int idx;//dfs计数
int dfn[maxn]; //表示dfs遍历到该节点的序号,也就是顺序值
int low[maxn]; //表示当前顶点不通过父亲节点能访问到的祖先节点(父亲节点上面的节点)中的最小顺序值
vector< pair<int ,int > > bridge;
bool cmp(pair<int ,int > a, pair<int ,int > b){
if(a.first == b.first) return a.second < b.second;
return a.first < b.first;
}
void addEdge(int u, int v)
{
edge[cnt].to = v;
edge[cnt].next = head[u];
head[u] = cnt++;
}
//以u为根节点dfs过程中产生一棵搜索树
void Tarjan(int u, int pre)
{
dfn[u] = low[u] = ++idx;
for(int i = head[u]; i; i = edge[i].next)
{
int v = edge[i].to;
//if(v == pre) continue;
if(!dfn[v])
{
Tarjan(v, u);
low[u] = min(low[u] , low[v]);
if(low[v] > dfn[u] ){ //(u,v)边是桥
bridge.push_back(make_pair(min(u , v) , max(u , v)));
}
}
else if(v != pre)
low[u] = min(low[u], dfn[v]);
}
}
void init(){
bridge.clear();
ans = 0;
cnt = 1;
memset(head, 0, sizeof(head));
idx = 0;
memset(dfn, 0, sizeof(dfn));
memset(low, 0, sizeof(low));
}
int main()
{
int n;
while(scanf("%d", &n) != EOF)
{
if(n == 0){
printf("0 critical links\n");
printf("\n");
continue;
}
init();
int u, v, x;
for(int i=1; i<=n ;i++){
scanf("%d (%d)",&u, &x);
for(int i=1; i<=x; i++){
scanf("%d",&v);
addEdge(u , v);
addEdge(v , u);
}
}
for(int i=0; i<n; i++){
if(!dfn[i]) Tarjan(i, i);
}
sort(bridge.begin(), bridge.end(), cmp);
printf("%d critical links\n",bridge.size());
for(int i=0; i<bridge.size(); i++){
printf("%d - %d\n",bridge[i].first, bridge[i].second);
}
printf("\n");
}
return 0;
}