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

传送门
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;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Hexrt

客官,请不要给我小费!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值