P4258 [WC2016]挑战NPC【带花树】

191 阅读3分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

传送门 nn个球,mm个筐,每个筐最多装33个 每个球都必须放入筐中,每个球只能放它给出的筐中 要求最多有多少个筐装了<=1<=1个球 并且给出放的方案(题目保证存在一种方案球都能放完)

分析

像这种匹配问题 有二分图:匈牙利算法,网络流等,一般图:带花树

对这道题分析一下,首先是经典的条件限制(球只能放某些筐中,筐最多放多少),我们要求答案是,最多的筐满足条件 对于二分图来说,如果要求最多多少球能够放入到筐中,倒是能够解决 但是这里都保证了所有球都得放完,所以目前来看应该不是二分图问题

从何入手呢?我们要求最多放<=1<=1个的筐 对于满足条件的(0011)情况

  • 对于 00 的情况,有00个球与筐相关的点连边,剩下容量为 33,贡献 11
  • 对于 11 的情况,有11个球与筐相关的点连边,剩下容量为 22,贡献 11

对于不满足条件的(2233)情况

  • 对于 22 的情况,有 22 个球与筐相关的点连边,剩下容量 11,贡献 00
  • 对于 33 的情况,有 33 个球与筐相关的点连边,剩下容量 00,贡献 00

思考一下,如果一般的建图解决的话,这个最多3个的条件,通常是在网络流里面流量限制,要么拆点,将筐拆成三个点,表示容量。

我们猜测这题用一般图来做的前提下(二分图目前看解决不了) 观察拆出来的这三个点的不同情况,将这三个点两两连边

  • 对于 00 的情况,剩下 33个点,一般图匹配答案为 11,真实答案贡献 11
  • 对于 11 的情况,剩下 22个点,一般图匹配答案为 11,真实答案贡献 11

对于不满足条件的(2233)情况

  • 对于 22 的情况,剩下 11个点,一般图匹配答案为 00,真实答案贡献 00
  • 对于 33 的情况,剩下 00个点,一般图匹配答案为 00,真实答案贡献 00

这里发现,和这三个点的匹配有关系 但是一般图匹配我们需要 球的点 参与才行啊 将球的点带入继续观察

  • 匹配了00个点,球点和筐点匹配答案为 00筐点自身匹配答案为11合计 00,真实答案贡献为 11
  • 匹配了11个点,球点和筐点匹配答案为 11筐点自身匹配答案为11合计 22,真实答案贡献为 11
  • 匹配了22个点,球点和筐点匹配答案为 22筐点自身匹配答案为00合计 22,真实答案贡献为 00
  • 匹配了33个点,球点和筐点匹配答案为 33筐点自身匹配答案为00合计 33,真实答案贡献为 00

发现,真实答案贡献的总和 == 总匹配数 - 参与匹配的球点数 而此题保证,所有球都放入了 则答案==匹配数-球数量(NN)

细节:注意最后输出答案,要根据拆点的实际情况还原

代码

//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;
}