总结:坐到后面,才感觉tarjan算法其实是一种思想,在不同情况下需要灵活运用。
本专题所涉及的内容比较多,每个题目的类型都比较经典,所以每个题都感觉很重要。
注意:
1,有的时候题目并没有说清楚有没有重边,有需要的话就在tarjan里面加一点判断。
2,缩点的方式有很多,在tarjan的时候可以缩,也可以tarjan之后用并查集缩。
3,感觉连通图的题的代码有点长,慢慢写别写错就好。。
A - Network of Schools
问题描述:给一个有向图,A问题问至少选多少个点可以到达所有的点。B问题询问至少加多少条边可以使得这个图是强连通图。
首先解决A问题,我们可以用tarjan算法来算出各个连通分量,然后再计算每个连通分量的入度和出度,那么入度等于0的连通分量的个数也就是A问题的答案。问题B我们则取入度为0的个数和出度为0的个数中的最大值即可。我们可以这样分析:将一个出度为0的点a连向一个入度为0的点b,并且b是a的祖先(b可以到a),那么他们就组成了一个环,这条路径上的点也就强连通了,那么我们如果错开相连,a连向一个入度为0的点c(c不是a的祖先),再用c可到达的出度为0的点d连向b的话,他们就组成了一个环,也就变成强连通了,基于这样的连法,所要加的边的条数就是入度为0的个数和出度为0的个数的最大值了。
#include<iostream>
#include<algorithm>
#include<cstring>
#include<stdio.h>
using namespace std;
const int maxn = 105;
struct Edge
{
int u, v, next;
Edge(int _u,int _v,int _nxt):u(_u),v(_v),next(_nxt){}
Edge(){}
}edges[maxn*maxn];
int head[maxn],tot;
void addedge(int u, int v)
{
edges[tot] = Edge(u, v, head[u]);
head[u] = tot++;
}
int DFN[maxn], LOW[maxn],vis[maxn],id;
int belong[maxn], cnt;
int stack[maxn],tot1;
void tarjan(int u)
{
DFN[u] = LOW[u] = ++id;
stack[tot1++] = u;
vis[u] = 1;
for (int i = head[u];~i;i = edges[i].next)
{
int v = edges[i].v;
if (!DFN[v])
{
tarjan(v);
LOW[u] = min(LOW[u], LOW[v]);
}
else if (vis[v])
LOW[u] = min(LOW[u], DFN[v]);
}
if (LOW[u] == DFN[u])
{
cnt++;
int v;
do
{
v = stack[tot1 - 1];
vis[v] = 0;
belong[v] = cnt;
tot1--;
} while (v!=u);
}
}
int in[maxn], out[maxn];
int main()
{
memset(head, -1, sizeof(head));
int n;
scanf("%d", &n);
int tmp;
for (int i = 1;i <= n;i++)
{
while (~scanf("%d",&tmp)&&tmp)
{
addedge(i, tmp);
}
}
for (int i = 1;i <= n;i++)if (!DFN[i])
id=0,tarjan(i);
for (int i = 1;i <= n;i++)
{
for (int j = head[i];~j;j = edges[j].next)
{
if (belong[i] != belong[edges[j].v])
out[belong[i]]++, in[belong[edges[j].v]]++;
}
}
int one = 0, two = 0;
for (int i = 1;i <= cnt;i++)
{
if (!in[i])one++;
if (!out[i])two++;
}
if (cnt == 1)
{
cout << 1 << endl;
cout << 0 << endl;
return 0;
}
cout << one << endl;
cout << max(one, two) << endl;
}
B - Network
问题描述:求割点。
所谓割点就是将这个点及与这个点相连的边都删掉的话,能使强连通分量增多的点。
一个点是割点有两个判断条件。
1,如果它是根的话,它的分支>1。
2,如果不是根,DFN[fa[u]]<=LOW[u]。
#include<iostream>
#include<algorithm>
#include<cstring>
#include<stdio.h>
using namespace std;
const int maxn = 105;
struct Edge
{
int u, v, next;
Edge(int _u,int _v,int _nxt):u(_u),v(_v),next(_nxt){}
Edge(){}
}edges[maxn*maxn];
int head[maxn], tot;
void addedge(int u, int v)
{
edges[tot] = Edge(u, v, head[u]);
head[u] = tot++;
}
int DFN[maxn], LOW[maxn], id;
int N;
bool chose[maxn];
int fa[maxn];
void tarjan(int u, int pre)
{
fa[u] = pre;
DFN[u] = LOW[u] = ++id;
for (int i = head[u];~i;i = edges[i].next)
{
int v = edges[i].v;
if (!DFN[v])
{
tarjan(v, u);
LOW[u] = min(LOW[u], LOW[v]);
}
else if (v != pre)
LOW[u] = min(LOW[u], DFN[v]);
}
}
void init()
{
id = 0;
tot = 0;
memset(head, -1, sizeof(head));
memset(DFN, 0, sizeof(DFN));
memset(LOW, 0, sizeof(LOW));
memset(chose, 0, sizeof(chose));
}
void solve()
{
tarjan(1, 0);
int rootnum = 0;
for (int i = 1;i <= N;i++)
{
if (fa[i] == 1)rootnum++;
else if (LOW[i] >= DFN[fa[i]])
chose[fa[i]] = 1;
}
int ans = 0;
for (int i = 2;i <= N;i++)
if (chose[i])
ans++;
if (rootnum > 1)
ans++;
printf("%d\n", ans);
}
int main()
{
while (~scanf("%d",&N)&&N)
{
init();
char s;
int tmp;
while (1)
{
scanf("%d%c", &tmp, &s);
if (!tmp)break;
int tmp2;
while (~scanf("%d%c",&tmp2,&s))
{
addedge(tmp, tmp2);
addedge(tmp2, tmp);
if (s == '\n')break;
}
}
solve();
}
}
C - Critical Links
问题描述:求桥。桥的定义:删掉这条边会使强连通分量增多。
桥的判断只有1个。
当DFN[fa[u]] < LOW[u]时,这条边就是桥。
#include<iostream>
#include<algorithm>
#include<stdio.h>
#include<cstring>
#include<set>
using namespace std;
const int maxn = 505;
struct Edge
{
int u, v, next;
Edge(int _u,int _v,int _n):u(_u),v(_v),next(_n){}
Edge(){}
}edges[maxn*maxn];
int head[maxn], tot;
void addedge(int u, int v)
{
edges[tot] = Edge(u, v, head[u]);
head[u] = tot++;
}
int deg[maxn];
int DFN[maxn], LOW[maxn], id;
int n;
pair<int, int>pii[maxn*maxn];
int pcnt;
int fa[maxn];
void init()
{
tot = 0;
id = 0;
memset(head, -1, sizeof(head));
memset(deg, 0, sizeof(deg));
memset(DFN, 0, sizeof(DFN));
pcnt = 0;
}
void tarjan(int u,int pre)
{
DFN[u] = LOW[u] = ++id;
fa[u] = pre;
for (int i = head[u];~i;i = edges[i].next)
{
int v = edges[i].v;
if (!DFN[v])
{
tarjan(v, u);
LOW[u] = min(LOW[u], LOW[v]);
}
else if (v != pre)
LOW[u] = min(LOW[u], DFN[v]);
}
}
void solve()
{
for (int i = 0;i < n;i++)if(!DFN[i])
{
id = 0;
tarjan(i,-1);
}
int ans = 0;
for (int i = 0;i < n;i++)
{
if (fa[i] == -1)continue;
if (DFN[fa[i]] < LOW[i])
{
ans++;
int themin = min(fa[i], i);
int themax = max(fa[i], i);
pii[pcnt++] = make_pair(themin, themax);
}
}
sort(pii, pii + pcnt);
printf("%d critical links\n", ans);
for (int i = 0;i < pcnt;i++)
printf("%d - %d\n", pii[i].first, pii[i].second);
puts("");
}
int main()
{
while (~scanf("%d",&n))
{
init();
int u;
for (int i = 1;i <= n;i++)
{
scanf("%d", &u);
char s[100];
scanf("%s", s);
int tmp;
sscanf(s, "(%d)", &tmp);
int v;
while (tmp--)
{
scanf("%d", &v);
deg[u]++, deg[v]++;
addedge(u, v);
addedge(v, u);
}
}
solve();
}
}
D - Network
问题描述:给一个无向图,每次询问加一条边,问图中还有多少个桥。
首先我们可以把桥算出来,注意我们在用tarjan的时候,其实把这个图按照时间戳的顺序变成了一个树,那么再加u到v的边的时候其实就是就相当于销毁树上u点到v点这条路上的所有的桥。这里我们其实可以用并查集优化一下求lca的过程。
#include<iostream>
#include<stdio.h>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn = 100005;
const int maxm = 200005;
struct Edge
{
int u, v, next;
Edge(int _u,int _v,int _nxt):u(_u),v(_v),next(_nxt){}
Edge(){}
}edges[maxm*2];
int head[maxn], tot;
void addedge(int u, int v)
{
edges[tot] = Edge(u, v, head[u]);
head[u] = tot++;
}
int DFN[maxn], LOW[maxn], dep[maxn],fa[maxn], id;
int cut[maxn];
int f[maxn];
int N, M;
void init()
{
tot = 0;id = 0;
memset(head, -1, sizeof(head));
memset(DFN, 0, sizeof(DFN));
memset(dep, 0, sizeof(dep));
memset(cut, 0, sizeof(cut));
for (int i = 1;i <= N;i++)
f[i] = i;
}
void tarjan(int u,int pre)
{
DFN[u] = LOW[u] = ++id;
fa[u] = pre;
dep[u] = dep[pre] + 1;
for (int i = head[u];~i;i = edges[i].next)
{
int v = edges[i].v;
if (!DFN[v])
{
tarjan(v, u);
LOW[u] = min(LOW[u], LOW[v]);
}
else if(v!=pre)
LOW[u] = min(LOW[u], DFN[v]);
}
}
int find(int a) { return a == f[a] ? a : f[a] = find(f[a]); }
int solve()
{
tarjan(1, 0);
int ans = 0;
for (int i = 2;i <= N;i++)
{
if (DFN[fa[i]] < LOW[i])
{
cut[i] = 1, ans++;
}
else
{
int u = find(i), v = find(fa[i]);
f[u] = v;
}
}
return ans;
}
int query(int u, int v)
{
int ans = 0;
u = find(u), v = find(v);
while (u!=v)
{
if (dep[u] > dep[v])
{
int fu = find(fa[u]);
f[u] = fu;
u = fu;
}
else
{
int fv = find(fa[v]);
f[v] = fv;
v = fv;
}
ans++;
}
return ans;
}
int main()
{
int cas = 1;
while (~scanf("%d %d",&N,&M)&&N)
{
init();
int u, v;
for (int i = 1;i <= M;i++)
{
scanf("%d %d", &u, &v);
addedge(u, v);
addedge(v, u);
}
int ans=solve();
int q;
scanf("%d", &q);
printf("Case %d:\n", cas++);
while (q--)
{
scanf("%d %d", &u, &v);
ans -= query(u, v);
printf("%d\n", ans);
}
puts("");
}
return 0;
}
E - Redundant Paths
问题描述:给一个无向图,问至少加多少条边使得图上没有桥。
我们可以在tarjan的时候处理一下,将不是桥的边连的点缩一下。然后我们再求缩完点之后的每个点的度,记度为1的点的数量为num,那么ans=(num+1)/2。读者可以自行想一想为什么。
#include<iostream>
#include<algorithm>
#include<stdio.h>
#include<cstring>
using namespace std;
const int maxn = 5005;
const int maxm = 5005;
struct Edge
{
int u, v, next;
Edge(int _u,int _v,int _nxt):u(_u),v(_v),next(_nxt){}
Edge(){}
}edges[maxm * 2];
int head[maxn], tot;
void addedge(int u, int v)
{
edges[tot] = Edge(u, v, head[u]);
head[u] = tot++;
}
int DFN[maxn], LOW[maxn], vis[maxn], belong[maxn], fa[maxn], id;
int kind;
int stack[maxn], cnt;
void tarjan(int u, int pre)
{
DFN[u] = LOW[u] = ++id;
fa[u] = pre;
vis[u] = 1;
stack[cnt++] = u;
for (int i = head[u];~i;i = edges[i].next)
{
int v = edges[i].v;
if (v == pre)continue;
if (!DFN[v])
{
tarjan(v, u);
LOW[u] = min(LOW[u], LOW[v]);
}
else if (vis[v] == 1)
{
LOW[u] = min(LOW[u], DFN[v]);
}
}
if (LOW[u] == DFN[u])
{
kind++;
int v;
do
{
v = stack[cnt - 1];
belong[v] = kind;
cnt--;
vis[v] = 0;
} while (v!=u);
}
}
int deg[maxn];
int n, m;
void solve()
{
tarjan(1, 0);
for (int i = 2;i <= n;i++)
{
int a = belong[i], b = belong[fa[i]];
if (a != b)
deg[a]++, deg[b]++;
}
int ans = 0;
for (int i = 1;i <= kind;i++)
if (deg[i] == 1)
ans++;
printf("%d\n", (ans + 1) / 2);
}
int main()
{
memset(head, -1, sizeof(head));
scanf("%d %d", &n, &m);
int u, v;
for (int i = 1;i <= m;i++)
{
scanf("%d %d", &u, &v);
addedge(u, v);
addedge(v, u);
}
solve();
}
F - Warm up
问题描述:给一个无向图,加一条边使得图的桥最少,问最少可以是多少。
我们仍然可以先求出桥,然后进行缩点。对缩完点之后的树求一下直径就行了。
#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>
#include<stdio.h>
#include<queue>
using namespace std;
const int maxn = 200005;
const int maxm = 1000005;
struct Edge
{
int u, v, next;
Edge(int _u,int _v,int _nxt):u(_u),v(_v),next(_nxt){}
Edge(){}
}edges[maxm * 2];
int head[maxn], tot;
void addedge(int u, int v)
{
edges[tot] = Edge(u, v, head[u]);
head[u] = tot++;
}
int cut[maxn];
int f[maxn];
int DFN[maxn], LOW[maxn], fa[maxn], id;
int n, m;
void init()
{
tot = 0;
id = 0;
memset(head, -1, sizeof(head));
memset(cut, 0, sizeof(cut));
memset(DFN, 0, sizeof(DFN));
}
void tarjan(int u, int pre)
{
DFN[u] = LOW[u] = ++id;
fa[u] = pre;
bool flag = true;
for (int i = head[u];~i;i = edges[i].next)
{
int v = edges[i].v;
if (v == pre&&flag) { flag = false;continue; }
if (!DFN[v])
{
tarjan(v, u);
LOW[u] = min(LOW[u], LOW[v]);
}
else
LOW[u] = min(LOW[u], DFN[v]);
}
}
int find(int a) { return a == f[a] ? a : f[a] = find(f[a]); }
void change(int u)
{
while (u!=1&&!cut[u])
{
int f2 = find(fa[u]);
f[u] = f2;
u = f2;
}
}
int d[maxn];
struct node
{
int u, dist;
node(int _u,int _d):u(_u),dist(_d){}
node(){}
bool operator<(const node &b)const
{
return dist < b.dist;
}
};
bool vis[maxn];
void bfs(int u)
{
memset(vis, 0, sizeof(vis));
priority_queue<node>Q;
Q.push(node(u, 0));
while (!Q.empty())
{
node x = Q.top();Q.pop();
if (vis[x.u])continue;
vis[x.u] = 1;
for (int i = head[x.u];~i;i = edges[i].next)
{
int v = edges[i].v;
if (d[v] < d[x.u] + 1)
{
d[v] = d[x.u] + 1;
Q.push(node(v, d[v]));
}
}
}
}
void solve()
{
tarjan(1, 0);
int num = 0;
for (int i = 2;i <= n;i++)
{
int f = fa[i];
if (DFN[f] < LOW[i])
{
num++;
cut[i] = 1;
}
}
for (int i = 1;i <= n;i++)f[i] = i;
for (int i = 1;i <= n;i++)if (f[i] == i)
change(i);
memset(head, -1, sizeof(head));
tot = 0;
int edgenum = 0;
for (int i = 2;i<= n;i++)if (cut[i])
{
edgenum++;
int u = i, v = fa[i];
int fu = find(u), fv = find(v);
addedge(fu, fv);
addedge(fv, fu);
}
for (int i = 1;i <= n;i++)
d[i] = 0;
bfs(1);
int themax = 0, idx;
for(int i=1;i<=n;i++)
if (d[i] > themax)
{
themax = d[i];
idx = i;
}
for (int i = 1;i <= n;i++)
d[i] = 0;
bfs(idx);
themax = 0;
for (int i = 1;i <= n;i++)
if (d[i] > themax)
{
themax = d[i];
idx = i;
}
printf("%d\n", edgenum - themax);
}
int main()
{
while (~scanf("%d %d",&n,&m)&&n)
{
init();
int u, v;
for (int i = 1;i <= m;i++)
{
scanf("%d %d", &u, &v);
addedge(u, v);
addedge(v, u);
}
solve();
}
}
G - Strongly connected
问题描述:给一个简单的有向图,问最多可以加多少条边,使得图仍然不是强连通图。
最终添加完边的图,肯定可以分成两个部X和Y,其中只有X到Y的边没有Y到
X的边,那么要使得边数尽可能的多,则X部肯定是一个完全图,Y部也是,同时X部
中每个点到Y部的每个点都有一条边,假设X部有x个点,Y部有y个点,有x+y=n,同时
边数F=x*y+x*(x-1)+y*(y-1),整理得:F=N*N-N-x*y,(然后去掉已经有了的边m,就是
答案),当x+y为定值时,二者越接近,x*y越大,所以要使得边数最多,那么X部和Y部
的点数的个数差距就要越大,所以首先对于给定的有向图缩点,对于缩点后的每个点,
如果它的出度或者入度为0,那么它才有可能成为X部或者Y部,所以只要求缩点之后的
出度或者入度为0的点中,包含节点数最少的那个点,令它为一个部,其它所有点加起
来做另一个部,就可以得到最多边数的图了。
思路转自:
http://www.cnblogs.com/jackge/p/3231767.html
#include<iostream>
#include<algorithm>
#include<cstring>
#include<stdio.h>
using namespace std;
const int maxn = 100005;
const int maxm = 100005;
typedef long long ll;
struct Edge
{
int u, v, next;
Edge(int _u,int _v,int _nxt):u(_u),v(_v),next(_nxt){}
Edge(){}
}edges[maxm];
int head[maxn], tot;
void addedge(int u, int v)
{
edges[tot] = Edge(u, v, head[u]);
head[u] = tot++;
}
int DFN[maxn], LOW[maxn], vis[maxn], id;
int n, m;
int stack[maxn], cnt;
int belong[maxn], bcnt;
int num[maxn];
int in[maxn], out[maxn];
void init()
{
tot = 0;
memset(head, -1, sizeof(head));
memset(DFN, 0, sizeof(DFN));
id = 0;bcnt = 0;
memset(num, 0, sizeof(num));
memset(in, 0, sizeof(in));
memset(out, 0, sizeof(out));
}
void tarjan(int u)
{
DFN[u] = LOW[u] = ++id;
vis[u] = 1;
stack[cnt++] = u;
for (int i = head[u];~i;i = edges[i].next)
{
int v = edges[i].v;
if (!DFN[v])
{
tarjan(v);
LOW[u] = min(LOW[u], LOW[v]);
}
else if (vis[v])
LOW[u] = min(LOW[u], DFN[v]);
}
if (LOW[u] == DFN[u])
{
int v;
bcnt++;
do
{
v = stack[cnt - 1];
cnt--;
vis[v] = 0;
belong[v] = bcnt;
} while (v!=u);
}
}
void solve()
{
for (int i = 1;i <= n;i++)if (!DFN[i])
id=0,tarjan(i);
for (int i = 1;i <= n;i++)
num[belong[i]]++;
for (int i = 1;i <= n;i++)
{
for (int j = head[i];~j;j = edges[j].next)
{
int v = edges[j].v;
if (belong[i] != belong[v])
out[belong[i]]++, in[belong[v]]++;
}
}
int Y = n;
for (int i = 1;i <= bcnt;i++)if(in[i]==0||out[i]==0)
{
Y = min(Y, num[i]);
}
if (Y == n)
{
puts("-1");
return;
}
ll ans = 1LL * n*n - n - 1LL * (n - Y)*Y - m;
printf("%lld\n", ans);
}
int main()
{
int T;
scanf("%d",&T);
int cas = 1;
while (T--)
{
scanf("%d %d", &n, &m);
init();
int u, v;
for (int i = 1;i <= m;i++)
{
scanf("%d %d", &u, &v);
addedge(u, v);
}
printf("Case %d: ", cas++);
solve();
}
}
I - Caocao’s Bridges
问题描述:求桥边权最小是多少。
这题有几个坑点。。
1,如果一开始就不是强连通图,那么输出0。
2,如果边权最小为0,那么应该输出1,因为要派1个人去。。。
#include<iostream>
#include<algorithm>
#include<cstring>
#include<stdio.h>
using namespace std;
const int maxn = 1005;
const int maxm = maxn*maxn;
struct Edge
{
int u, v,cost, next;
Edge(int _u,int _v,int _c,int _nxt):u(_u),v(_v),cost(_c),next(_nxt){}
Edge(){}
}edges[maxm * 2];
int head[maxn], tot;
void addedge(int u, int v,int c)
{
edges[tot] = Edge(u, v, c,head[u]);
head[u] = tot++;
}
int DFN[maxn], LOW[maxn], id;
int ans;
void init()
{
ans = maxm;
tot = 0, id = 0;
memset(head, -1, sizeof(head));
memset(DFN, 0, sizeof(DFN));
}
void tarjan(int u, int pre)
{
DFN[u] = LOW[u] = ++id;
bool flag = true;
for (int i = head[u];~i;i = edges[i].next)
{
int v = edges[i].v;
if (v == pre&&flag) { flag = false;continue; }
if (!DFN[v])
{
tarjan(v, u);
LOW[u] = min(LOW[u], LOW[v]);
if (DFN[u] < LOW[v])
ans = min(ans, edges[i].cost);
}
else
LOW[u] = min(LOW[u], DFN[v]);
}
}
int n, m;
void solve()
{
tarjan(1, 0);
for (int i = 1;i <= n;i++)if (DFN[i] == 0) { puts("0");return; }
if (ans == maxm)puts("-1");
else if (ans == 0)puts("1");
else printf("%d\n", ans);
}
int main()
{
while (~scanf("%d %d", &n, &m)&&n)
{
init();
int u, v, c;
for (int i = 1;i <= m;i++)
{
scanf("%d %d %d", &u, &v, &c);
addedge(u, v, c);
addedge(v, u, c);
}
solve();
}
}
H - Prince and Princess
思路转自这里
主要构图转化的思路很神奇,我看了题解才会的,T_T
首先n,m个点做一次二分匹配,得到匹配数最大为res. 相当于右边有m-res个点没有匹配,左边有n-res个点没有匹配。
所以在左加m-res个点,和右边所有相连。
在右边加n-res个点,个左边所有相连。
然后做n+m-res,n+m-res个点的二分匹配。
匹配数肯定是n+m-res;
主要是得到匹配的情况。
对于左边的点i. 把i相匹配的在右边的点,向其余和i相连的点连一有向边。
然后做强连通缩点。
如果边v1,v2在同一个连通分量里,那么v1,v2匹配的u1,u2可以交换。
#include<iostream>
#include<algorithm>
#include<cstring>
#include<stdio.h>
#include<queue>
#include<vector>
using namespace std;
const int maxn = 1005;
const int inf = 0x3f3f3f3f;
bool map[maxn][maxn];
int cx[maxn], cy[maxn];
int dx[maxn], dy[maxn];
bool used[maxn];
int dist;
int n, m;
int nL, nR;
bool bfs()
{
queue<int>Q;
memset(dy, -1, sizeof(dy));
memset(dx, -1, sizeof(dx));
for (int i = 1;i <= nL;i++)if (cx[i] == -1)
Q.push(i), dx[i] = 0;
dist = inf;
while (!Q.empty())
{
int u = Q.front();Q.pop();
if (dx[u] > dist)break;
for (int i = 1;i <= nR;i++)if (map[u][i] && dy[i]==-1)
{
dy[i] = dx[u] + 1;
if (cy[i] == -1)dist = dy[i];
else
{
dx[cy[i]] = dy[i] + 1;
Q.push(cy[i]);
}
}
}
return dist != inf;
}
int find(int u)
{
for (int i = 1;i <= nR;i++)if (map[u][i] && !used[i]&&dy[i]==dx[u]+1)
{
used[i] = 1;
if (cy[i] != -1 && dy[i] == dist)continue;
else if (cy[i] == -1 || find(cy[i]))
{
cx[u] = i;
cy[i] = u;
return 1;
}
}
return 0;
}
int MaxMatch()
{
memset(cx, -1, sizeof(cx));
memset(cy, -1, sizeof(cy));
int ans = 0;
while (bfs())
{
memset(used, 0, sizeof(used));
for (int i = 1;i <= nL;i++)
if (cx[i] == -1)
ans += find(i);
}
return ans;
}
int DFN[maxn], LOW[maxn],id;
int stack[maxn], s;
int belong[maxn], bnum;
bool vis[maxn];
int link[maxn][maxn];
void tarjan(int u)
{
DFN[u] = LOW[u] = ++id;
stack[s++] = u;
vis[u] = 1;
for (int i = 1;i <= nR;i++)if (link[u][i])
{
if (!DFN[i])
{
tarjan(i);
LOW[u] = min(LOW[u], LOW[i]);
}
else if(vis[i])
LOW[u] = min(LOW[u], DFN[i]);
}
if (DFN[u] == LOW[u])
{
bnum++;
int v;
do
{
v = stack[s - 1];
vis[v] = 0;
s--;
belong[v] = bnum;
} while (v!=u);
}
}
void solve()
{
for (int i = 1;i <= nR;i++)if (!DFN[i])
id=0,tarjan(i);
}
vector<int>V;
void init()
{
memset(DFN, 0, sizeof(DFN));
memset(map, 0, sizeof(map));
memset(link, 0, sizeof(link));
id = 0;bnum = 0;
}
int main()
{
int T;
scanf("%d", &T);
int cas = 1;
while (T--)
{
scanf("%d %d", &n, &m);
init();
int tmp;
for (int i = 1;i <= n;i++)
{
int times;
scanf("%d", ×);
while (times--)
{
scanf("%d", &tmp);
map[i][tmp] = 1;
}
}
nL = n, nR = m;
int res = MaxMatch();
int num = n + m - res;
nL = nR = num;
for (int i = n + 1;i <= num;i++)
for (int j = 1;j <= num;j++)
map[i][j] = 1;
for (int i = 1;i <= n;i++)
for (int j = m + 1;j <= num;j++)
map[i][j] = 1;
MaxMatch();
for (int i = 1;i <= num;i++)
{
int to = cx[i];
for (int j = 1;j <= num;j++)if(map[i][j])
link[to][j] = 1;
}
solve();
printf("Case #%d:\n", cas++);
for (int i = 1;i <= n;i++)
{
V.clear();
int to = cx[i];
int blong = belong[to];
for (int j = 1;j <= m;j++)if (map[i][j] && belong[j] == blong)
V.push_back(j);
printf("%d", V.size());
for (int i = 0;i < V.size();i++)
printf(" %d", V[i]);
puts("");
}
}
return 0;
}