【UOJ#171】【WC2016】挑战NPC【带花树】

vfk出的(好)题。赛时无人AC,一大堆人60分...


前两个点,暴搜即可。

第三个点,贪心。

第四到六个点,最大流。

std:带花树


把一个篮子拆为3个槽,每两个槽之间互相连边(vfk:无关紧要的小优化,只连其中两个槽即可),每个球与可放的篮子的三个槽都连边,直接跑带花树即可。

答案减去球的个数就是答案。


附vfk讲解:

筐子内装的球不超过 3 个意味着可以看做每个筐子有三个槽,每个槽可以放一个球,于是就变成了球和槽进行匹配,b 1 k , b 2 k , b 3 k 就代表了这三个槽。
原问题的解对应一个匹配:如果 b 1 k , b 2 k , b 3 k 中有不超过 1 个匹配点,那么三元环内部可以产生一条匹配边;如果匹配点超过 1 个则内部无法产生一条匹配边。原问题的解可以对应到一个

匹配数 = 半空袋子数 + n 的匹配。
最大匹配对应一个原问题的解:先依次从 a 1 , . . . , a n 出发找增广路,再依次从筐子对应的结点出发找增广路,这样可以求得一个 a k均为匹配点的最大匹配,显然这对应了原问题的一个解。

上文中说,“把 b 1 k , b 2 k , b 3 k 连成一个三元环”,事实上,只要连边就行了。(b 1 k , b 2 k )
这是因为同一个筐子的槽是等价的。这样,一个最大匹配中某个筐子对应的结点中有一个匹配点的时候可以让 b 3 k 成为匹配点。


PS:附vfk的图



#include <cstdio>
#include <algorithm>

using namespace std;

const int maxn = 605, maxm = 100005, maxq = 10000;

int n, m, e, head[maxn], cnt, tot, match[maxn], fri[maxn], fa[maxn], top[maxn], ring[maxn], q[maxq];
bool odd[maxn], vis[maxn];

struct _edge {
	int v, next;
} g[maxm << 1];

inline int iread() {
	int f = 1, x = 0; char ch = getchar();
	for(; ch < '0' || ch > '9'; ch = getchar()) f = ch == '-' ? -1 : 1;
	for(; ch >= '0' && ch <= '9'; ch = getchar()) x = x * 10 + ch - '0';
	return f * x;
}

inline void add(int u, int v) {
	g[cnt] = (_edge) {v, head[u]};
	head[u] = cnt++;
}

inline int find(int x) {
	return fa[x] == x ? x : fa[x] = find(fa[x]);
}

int h, t, clo;

inline int lca(int x, int y) {
	clo++;
	for(; x; x = find(top[x])) ring[x] = clo;
	for(x = y; ring[x] != clo; x = find(top[x]));
	return x;
}

inline void blossom(int x, int y, int p) {
	for(; find(x) != find(p); x = fri[y]) {
		fri[x] = y; y = match[x];
		fa[find(x)] = fa[find(y)] = p;
		q[t++] = y; odd[y] = 0;
	}
}

inline bool dfs(int s) {
	for(int i = 1; i <= tot; i++) vis[i] = odd[i] = fri[i] = top[i] = 0, fa[i] = i;
	h = t = 0;
	vis[q[t++] = s] = 1;
	while(h != t) {
		int now = q[h++];
		for(int i = head[now]; ~i; i = g[i].next) {
			int v = g[i].v;
			if(!vis[v]) {
				top[v] = fri[v] = now; odd[v] = vis[v] = 1;
				if(!match[v]) {
					for(int x, y, j = v; j; ) {
						x = fri[j]; y = match[x];
						match[j] = x; match[x] = j; j = y;
					}
					return 1;
				}
				vis[match[v]] = 1; top[match[v]] = v; q[t++] = match[v];
			} else if(find(now) != find(v) && !odd[v]) {
				int p = lca(now, v);
				blossom(now, v, p); blossom(v, now, p);
			}
		}
	}
	return 0;
}

int main() {
	int T = iread();
	while(T--) {
		for(int i = 0; i < maxn; i++) head[i] = -1, match[i] = 0; cnt = 0;

		n = iread(); m = iread(); e = iread(); tot = n + m * 3;
		for(int i = 1; i <= e; i++) {
			int x = iread(), y = iread();
			for(int j = 1; j <= 3; j++) add(x, n + 3 * (y - 1) + j), add(n + 3 * (y - 1) + j, x);
		}
		for(int i = 1; i <= m; i++) add(n + 3 * (i - 1) + 1, n + 3 * (i - 1) + 2), add(n + 3 * (i - 1) + 2, n + 3 * (i - 1) + 1);

		int ans = 0;
		for(int i = 1; i <= tot; i++) if(!match[i]) ans += dfs(i);
		ans -= n;

		printf("%d\n", ans);
		for(int i = 1; i <= n; i++) printf("%d ", (match[i] - n - 1) / 3 + 1);
		printf("\n");
	}
	return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值