2018年全国多校算法寒假训练营练习比赛(第五场)D集合问题详解

链接:https://www.nowcoder.com/acm/contest/77/D
来源:牛客网

题目描述

给你a,b和n个数p[i],问你如何分配这n个数给A,B集合,并且满足:

若x在集合A中,则a-x必须也在集合A中。

若x在集合B中,则b-x必须也在集合B中。

输入描述:

第一行 三个数 n a b  1<=n<=1e5  1<=a,b<=1e9
第二行 n个数 p1 p2 p3...pn1<=pi<=1e9

输出描述:

如果可以恰好分开就输出第一行 YES
然后第二行输出 n个数 分别代表pi 是哪个集合的  0 代表是A集合 1代表是B 集合
不行就输出NO
放在哪个集合都可以的时候优先放B

示例1

输入

4 5 9

2 3 4 5

输出

YES

0 0 1 1

示例2

输入

3 3 4

1 2 4

输出

NO

这一题虽然是一个并查集的问题,但是这一题里面用到了非常复杂的原理。

首先这一题可能 出现重复的数字,可以证明,在每个重复的数字里面取一个,组成一个不同数字的集合,那么只要这个集合有符合要求的解,则原题一定有解,反之无解。

对于每个不同的数字,可以把它看成一个节点,每条边分为4类,a边相连的两个节点和为a,b边相连的两个节点的和为b,n边相连的两个节点一个为n,另外一个表示的数没有其他数和它的和为a,但是有数和它的和为b,n+1边相连的节点一个为n+1,另一个表示的数没有数和它的和为b,但是有数和它的和为a,

按照本算法,每个节点都可以被分到一个并查集里面取,每一个集合里面的数在以上叙述的图里面都可以通过某一条路径到达另外一个节点

点分为4类,否a有b(n+1类点),有a否b(n类点),否a否b,有a有b,当一个集合里面全是有a有b的节点的时候,这个集合里面的全部点都可以归为a类或者是b类,这里选择b类,当n和n+1同时在一个集合里面的时候,说面这个集合里面同时存在有a否b和否a有b的点,那么这个集合里面的点必然有一些分到a类,有一些分到b类,这里证明这样是不可能的:

假如即有分到a类的又有分到b类的点,那么a类的点所占的区域和b类的点所占的区域必定有分界线,必定有一条边把这两个区域连接起来,这条边不可能是a类边,因为这两条边两边的点分别是a类点和b类点,同样,这条边也不可能是b类边,原因同上,假设这条边是n类边,那么也不可能,n+1类边也不可能,也就是说一个集合里面不可能既有a类点也有b类点,也就是说当一个集合里面有n类点的时候,这个集合里面的点全部分到a类,当一个集合里面有n+1类点的时候,这个集合里面的点全部分到b类,当一个集合里面的点都不是n类点和n+1类点的时候,这个集合里面的点全部分到b类

假如存在以上所述的分配方式,那么一定存在解,反之,如果n和n+1在同一个集合里面的时候,存在否a否b点,这样的话一定不存在可行解,

#include <iostream>
#include <map>

using namespace std;
int fa[100003];
int j_find(int x)
{
	if (fa[x] == x)
	{
		return fa[x];
	}
	else
	{
		int tmp = j_find(fa[x]);
		fa[x] = tmp;
		return tmp;
	}
}

void merge(int x, int y)
{
	int a = j_find(x);
	int b = j_find(y);
	if (a != b)
	{
		fa[b] = a;
	}
}

int main() {
	int n, a, b,tmp;
	cin >> n >> a >> b;
	map<int,int> numMap;
	int num[100003];
	for (int i = 0; i < n; i++)
	{
		cin >> tmp;
		numMap.insert(make_pair(tmp, i)); 
		fa[i] = i;
	}
	fa[n] = n;
	fa[n + 1] = n + 1;
	map<int, int> ::iterator it;
	for (it = numMap.begin(); it != numMap.end(); it++)
	{
		if (numMap.count(a - it->first)) merge(it->second, numMap[a-it->first]);
		else merge(it->second, n);
		if (numMap.count(b - it->first)) merge(it->second, numMap[b - it->first]);
		else merge(it->second, n+1);
	}
	if (j_find(n) == j_find(n + 1))
	{
		cout << "NO"; return 0;
	}

	cout << "YES" << endl;
	for (int i = 0; i < n; i++)
	{
		if (j_find(i) == j_find(n + 1)) cout << 0;
		else cout << 1;
		if (i != n - 1) cout << " ";
	}
	return 0;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值