传送门
n
n
n个球,
m
m
m个筐,每个筐最多装
3
3
3个
每个球都必须放入筐中,每个球只能放它给出的筐中
要求最多有多少个筐装了
<
=
1
<=1
<=1个球
并且给出放的方案(题目保证存在一种方案球都能放完)
分析
像这种匹配问题
有二分图:匈牙利算法,网络流等,一般图:带花树
对这道题分析一下,首先是经典的条件限制(球只能放某些筐中,筐最多放多少),我们要求答案是,最多的筐满足条件
对于二分图来说,如果要求最多多少球能够放入到筐中,倒是能够解决
但是这里都保证了所有球都得放完,所以目前来看应该不是二分图问题
从何入手呢?我们要求最多放
<
=
1
<=1
<=1个的筐
对于满足条件的(
0
0
0或
1
1
1)情况
- 对于 0 0 0 的情况,有 0 0 0个球与筐相关的点连边,剩下容量为 3 3 3,贡献 1 1 1
- 对于 1 1 1 的情况,有 1 1 1个球与筐相关的点连边,剩下容量为 2 2 2,贡献 1 1 1
对于不满足条件的( 2 2 2 或 3 3 3)情况
- 对于 2 2 2 的情况,有 2 2 2 个球与筐相关的点连边,剩下容量 1 1 1,贡献 0 0 0
- 对于 3 3 3 的情况,有 3 3 3 个球与筐相关的点连边,剩下容量 0 0 0,贡献 0 0 0
思考一下,如果一般的建图解决的话,这个最多3个的条件,通常是在网络流里面流量限制,要么拆点,将筐拆成三个点,表示容量。
在我们猜测这题用一般图来做的前提下(二分图目前看解决不了)
观察拆出来的这三个点的不同情况,将这三个点两两连边
- 对于 0 0 0 的情况,剩下 3 3 3个点,一般图匹配答案为 1 1 1,真实答案贡献 1 1 1
- 对于 1 1 1 的情况,剩下 2 2 2个点,一般图匹配答案为 1 1 1,真实答案贡献 1 1 1
对于不满足条件的( 2 2 2 或 3 3 3)情况
- 对于 2 2 2 的情况,剩下 1 1 1个点,一般图匹配答案为 0 0 0,真实答案贡献 0 0 0
- 对于 3 3 3 的情况,剩下 0 0 0个点,一般图匹配答案为 0 0 0,真实答案贡献 0 0 0
这里发现,和这三个点的匹配有关系
但是一般图匹配我们需要 球的点 参与才行啊
将球的点带入继续观察
- 球匹配了 0 0 0个点,球点和筐点匹配答案为 0 0 0,筐点自身匹配答案为 1 1 1,合计 0 0 0,真实答案贡献为 1 1 1
- 球匹配了 1 1 1个点,球点和筐点匹配答案为 1 1 1,筐点自身匹配答案为 1 1 1,合计 2 2 2,真实答案贡献为 1 1 1
- 球匹配了 2 2 2个点,球点和筐点匹配答案为 2 2 2,筐点自身匹配答案为 0 0 0,合计 2 2 2,真实答案贡献为 0 0 0
- 球匹配了 3 3 3个点,球点和筐点匹配答案为 3 3 3,筐点自身匹配答案为 0 0 0,合计 3 3 3,真实答案贡献为 0 0 0
发现,真实答案贡献的总和
=
=
= 总匹配数
−
-
− 参与匹配的球点数
而此题保证,所有球都放入了
则答案
=
=
=匹配数
−
-
−球数量(
N
N
N)
细节:注意最后输出答案,要根据拆点的实际情况还原
代码
//P4258
/*
@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 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 <= N + 3*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() {
fill(head, head + 5 + N + 3 * M, -1);
fill(match, match + 5 + N + 3 * M, 0);
fill(dfn, dfn + 5 + N + 3 * M, 0);
tot = 0;
}
int arr[MAX_N];
void solve(){
cin >> N >> M >> K;
init();
int u, v;
for (int i = 1; i <= K; ++i) {
cin >> u >> v;
v = N + (v - 1)*3;
addEdge(u, v+1);
addEdge(u, v+2);
addEdge(u, v+3);
}
for (int i = 1; i <= M; ++i) {
v = N + (i - 1)*3;
addEdge(v+1, v+2);
addEdge(v+2, v+3);
addEdge(v+3, v+1);
}
int ans = 0;
for (int i = 1; i <= N + 3 * M; ++i) {
if (!match[i]) ans += aug(i);
}
cout << ans - N << "\n";
for (int i = 1; i <= N; ++i) {
cout << (match[i]-N-1)/3+1 << " ";
}
puts("");
}
signed main()
{
#ifndef ONLINE_JUDGE
//FILE_IN
FILE_OUT
#endif
int T = 1;cin >> T;
while (T--) solve();
return AC;
}