p s : 建 图 是 核 心 , 匹 配 都 是 一 样 的 ps:建图是核心,匹配都是一样的 ps:建图是核心,匹配都是一样的
//hopcroft_karp算法,复杂度O(sqrt(n)*m)
#include
#include
#include
#include
#include
#include
using namespace std;
const int N = 320;
const int INF = 0x3f3f3f3f;
struct edge
{
int to, next;
}e[N*N];
int match[N], head[N];
bool used[N];
int p, n;
int nx, ny, cnt, dis; //nx,ny分别是左点集和右点集的点数
int dx[N], dy[N], cx[N], cy[N]; //dx,dy分别维护左点集和右点集的标号
//cx表示左点集中的点匹配的右点集中的点,cy正好相反
void add(int u,int v)
{
e[cnt].to = v;
e[cnt].next = head[u];
head[u] = cnt++;
}
bool bfs() //寻找增广路径集,每次只寻找当前最短的增广路
{
queue<int> que;
dis = INF;
memset(dx, -1, sizeof dx);
memset(dy, -1, sizeof dy);
for(int i = 1; i <= nx; i++)//将未遍历的节点入队,并初始化次节点距离为0
if(cx[i] == -1)
{
que.push(i);
dx[i] = 0;
}
while(!que.empty())
{
int u = que.front();
que.pop();
if(dx[u] > dis) break;
for(int i = head[u]; i != -1; i = e[i].next)
{
int v = e[i].to;
if(dy[v] == -1)
{
dy[v] = dx[u] + 1;
if(cy[v] == -1) dis = dy[v]; //找到了一条增广路,dis为增广路终点的标号
else dx[cy[v]] = dy[v] + 1,que.push(cy[v]);
}
}
}
return dis != INF;
}
int dfs(int u)
{
for(int i = head[u]; i != -1; i = e[i].next)
{
int v = e[i].to;
if(! used[v] && dy[v] == dx[u] + 1) //如果该点没有被遍历过并且距离为上一节点+1
{
used[v] = true;
if(cy[v] != -1 && dy[v] == dis) continue; //u已被匹配且已到所有存在的增广路终点的标号,再递归寻找也必无增广路,直接跳过
if(cy[v] == -1 || dfs(cy[v]) )
{
cy[v] = u,cx[u] = v;
return 1;
}
}
}
return 0;
}
int hopcroft_karp()
{
int res = 0;
memset(cx, -1, sizeof cx);
memset(cy, -1, sizeof cy);
while(bfs())
{
memset(used, 0, sizeof used);
for(int i = 1; i <= nx; i++)
if(cx[i] == -1)res += dfs(i);
}
return res;
}
int main()
{
int t, a, b;
scanf("%d", &t);
while(t--)
{
cnt = 0;
memset(head, -1, sizeof head);
scanf("%d%d", &p, &n);
for(int i = 1; i <= p; i++)
{
scanf("%d", &a);
for(int j = 0; j < a; j++)
{
scanf("%d", &b);
add(i, b);
}
}
nx = p, ny = n;
if(hopcroft_karp() == p) printf("YES\n");
else printf("NO\n");
}
return 0;
}