#include <iostream>
//二分图的最大匹配问题,POJ149
//匈牙利算法
/*最大流算法的核心问题就是找增广路径(augment path)。匈牙利算法也不例外,它的基本模式就是:
初始时最大匹配为空
while 找得到增广路径
do 把增广路径加入到最大匹配中去
最小点覆盖数: 最小覆盖要求用最少的点(X集合或Y集合的都行)让每条边都至少和其中一个点关联。
可以证明:最少的点(即覆盖数)=最大匹配数
最小路径覆盖=最小路径覆盖=|N|-最大匹配数
用尽量少的不相交简单路径覆盖有向无环图G的所有结点。解决此类问题可以建立一个二分图模型。
把所有顶点i拆成两个:X结点集中的i和Y结点集中的i',
如果有边i->j,则在二分图中引入边i->j',设二分图最大匹配为m,则结果就是n-m。
*/
using namespace std;
bool g[110][310];//邻接矩阵,100门课程,300个学生。
bool flag,visited[310];//记录某个学生是否被访问过
int match[310];//记录与学生匹配的课程号
int p,n;//课程数和学生人数
int link[110];
bool dfs(int u){
for(int i =1;i<=n;i++){
if(g[u][i]&&!visited[i]){//如果节点i与节点u可以相连并且未被访问过
visited[i] = true;//标记其被查过
if(match[i]==-1 || dfs(match[i])) //如果i未在之前的匹配中,或者i在
//之前的匹配m中,但是从与i相邻的节点出发可以由增广路径
{
match[i] = u;//记录查找成功的记录,更新匹配
return true;
}
}
}
return false;
}
int main()
{
int i,j,k,t,v,ans;
cin>>t;
while(t--){
cin>>p>>n;
for(i=1;i<=p;i++){
for(j=1;j<=n;j++)
g[i][j] = false;
}
for(i=1;i<=n;i++)
match[i] = -1;
flag =true;
for(i=1;i<=p;i++){
cin>>k;
if(k==0)
flag =false;
while(k--){
cin>>v;
g[i][v] = true;
}
}
if(flag){
ans =0;
for(i=1;i<=p;i++){
for(j=1;j<=300;j++)
visited[j] = false;
if(dfs(i))
ans++;
}
if(ans==p){
cout<<"YES!"<<endl;
cout<<"最大匹配是:";
for(i=1;i<=300;i++){
if(match[i] !=-1){
link[match[i]] = i;
}
}
for(j =1;j<=p;j++){
cout<<j<<"->"<<link[j]<<endl;
}
}
else
cout<<"NO"<<endl;
}
else
cout<<"NO"<<endl;
}
//cout << "Hello world!" << endl;
return 0;
}
//二分图的最大匹配问题,POJ149
//匈牙利算法
/*最大流算法的核心问题就是找增广路径(augment path)。匈牙利算法也不例外,它的基本模式就是:
初始时最大匹配为空
while 找得到增广路径
do 把增广路径加入到最大匹配中去
最小点覆盖数: 最小覆盖要求用最少的点(X集合或Y集合的都行)让每条边都至少和其中一个点关联。
可以证明:最少的点(即覆盖数)=最大匹配数
最小路径覆盖=最小路径覆盖=|N|-最大匹配数
用尽量少的不相交简单路径覆盖有向无环图G的所有结点。解决此类问题可以建立一个二分图模型。
把所有顶点i拆成两个:X结点集中的i和Y结点集中的i',
如果有边i->j,则在二分图中引入边i->j',设二分图最大匹配为m,则结果就是n-m。
*/
using namespace std;
bool g[110][310];//邻接矩阵,100门课程,300个学生。
bool flag,visited[310];//记录某个学生是否被访问过
int match[310];//记录与学生匹配的课程号
int p,n;//课程数和学生人数
int link[110];
bool dfs(int u){
for(int i =1;i<=n;i++){
if(g[u][i]&&!visited[i]){//如果节点i与节点u可以相连并且未被访问过
visited[i] = true;//标记其被查过
if(match[i]==-1 || dfs(match[i])) //如果i未在之前的匹配中,或者i在
//之前的匹配m中,但是从与i相邻的节点出发可以由增广路径
{
match[i] = u;//记录查找成功的记录,更新匹配
return true;
}
}
}
return false;
}
int main()
{
int i,j,k,t,v,ans;
cin>>t;
while(t--){
cin>>p>>n;
for(i=1;i<=p;i++){
for(j=1;j<=n;j++)
g[i][j] = false;
}
for(i=1;i<=n;i++)
match[i] = -1;
flag =true;
for(i=1;i<=p;i++){
cin>>k;
if(k==0)
flag =false;
while(k--){
cin>>v;
g[i][v] = true;
}
}
if(flag){
ans =0;
for(i=1;i<=p;i++){
for(j=1;j<=300;j++)
visited[j] = false;
if(dfs(i))
ans++;
}
if(ans==p){
cout<<"YES!"<<endl;
cout<<"最大匹配是:";
for(i=1;i<=300;i++){
if(match[i] !=-1){
link[match[i]] = i;
}
}
for(j =1;j<=p;j++){
cout<<j<<"->"<<link[j]<<endl;
}
}
else
cout<<"NO"<<endl;
}
else
cout<<"NO"<<endl;
}
//cout << "Hello world!" << endl;
return 0;
}