传送门
清 楚 姐 姐 的 后 宫 有 很 多 妹 子 , 她 们 都 是 清 楚 姐 姐 的 翅 膀 。
当时觉得是匹配,就狂交了六十多发随机
题意:
N
N
N个妹子,
M
M
M个蝴蝶结
每个蝴蝶结能给给定的几个人
求获得
>
=
2
>=2
>=2个蝴蝶结的妹子的最多数量
分析
容易知道,妹子获得最多两个蝴蝶结就够了,再拿多,只会让其它妹子拿不到,答案会变劣
所以,对于妹子来说,两个就行了。
根据之前做题的经验P4258 [WC2016]挑战NPC【带花树】,这题明显二分图匹配做不了
考虑一般图
将妹子的限制,拆成两个点,并连边
分析一下,对于 1 个妹子来说
- 拿到了 0 0 0个 蝴蝶结,妹子剩下 2 2 2 个点,匹配数为 1 1 1,真实答案贡献 0 0 0
- 拿到了 1 1 1个 蝴蝶结,妹子剩下 1 1 1 个点,匹配数为 1 1 1,真实答案贡献 0 0 0
- 拿到了 2 2 2个 蝴蝶结,妹子剩下 0 0 0 个点,匹配数为 2 2 2,真实答案贡献 1 1 1
发现,对于一个妹子,要么匹配数为
1
1
1 要么是
2
2
2,只有 为
2
2
2的时候才会造成贡献
所以答案贡献为,匹配数
−
-
− 妹子数量
代码
//N18962F
/*
@Author: YooQ
*/
#include <bits/stdc++.h>
using namespace std;
#define sc scanf
#define pr printf
#define ll long long
#define int long long
#define FILE_OUT freopen("out", "w", stdout);
#define FILE_IN freopen("in", "r", stdin);
#define debug(x) cout << #x << ": " << x << "\n";
#define AC 0
#define WA 1
#define INF 0x3f3f3f3f
const ll MAX_N = 1e6+5;
const ll MOD = 1e9+7;
int N, M, K;
int arr[MAX_N];
int head[MAX_N];
int tot = 0;
struct Edge {
int to, nxt;
}edge[MAX_N];
void addEdge(int u, int v) {
edge[tot].nxt = head[u];
edge[tot].to = v;
head[u] = tot++;
edge[tot].nxt = head[v];
edge[tot].to = u;
head[v] = tot++;
}
int father[MAX_N];
int match[MAX_N];
int vis[MAX_N];
int pre[MAX_N];
int tim = 0;
int dfn[MAX_N];
queue<int>Q;
int find(int x) {
return x == father[x] ? x : father[x] = find(father[x]);
}
int LCA(int x, int y) {
++tim;
x = find(x);
y = find(y);
while (dfn[x] != tim) {
dfn[x] = tim;
x = find(pre[match[x]]);
if (y) swap(x, y);
}
return x;
}
void fix(int x) {
int nxt = 0;
while (x) {
nxt = match[pre[x]];
match[x] = pre[x];
match[pre[x]] = x;
x = nxt;
}
}
void blossom(int x, int y, int lca) {
while (find(x) != lca) {
pre[x] = y;
y = match[x];
if (vis[y] == 2) vis[y] = 1, Q.push(y);
if (find(x) == x) father[x] = lca;
if (find(y) == y) father[y] = lca;
x = pre[y];
}
}
bool aug(int u) {
while (Q.size()) Q.pop();
for (int i = 1; i <= 2 * N+M; ++i) {
father[i] = i;
pre[i] = vis[i] = 0;
}
Q.push(u);
vis[u] = 1;
int v;
while (Q.size()) {
u = Q.front();Q.pop();
for (int i = head[u];~i;i=edge[i].nxt) {
if (vis[v=edge[i].to] == 2 || find(u) == find(v)) continue;
if (!vis[v]) {
vis[v] = 2;
pre[v] = u;
if (!match[v]) {
fix(v);
return true;
}
vis[match[v]] = 1;
Q.push(match[v]);
} else {
int lca = LCA(u, v);
blossom(u, v, lca);
blossom(v, u, lca);
}
}
}
return false;
}
void init() {
memset(head, -1, sizeof head);
memset(match, 0, sizeof match);
tot = 0;
}
void solve(){
init();
cin >> N >> M;
int u, v;
for (int i = 1; i <= N; ++i) {
addEdge(i, N+i);
cin >> K;
while (K--) {
cin >> v;
addEdge(i, 2*N+v);
addEdge(N+i, 2*N+v);
}
}
int ans = 0;
for (int i = 1; i <= 2*N+M; ++i) {
if (!match[i]) ans += aug(i);
}
cout << ans - N << "\n";
}
signed main()
{
#ifndef ONLINE_JUDGE
//FILE_IN
FILE_OUT
#endif
int T = 1;cin >> T;
while (T--) solve();
return AC;
}