1. 程式人生 > >2018年全國多校演算法寒假訓練營練習比賽(第五場)D集合問題詳解

2018年全國多校演算法寒假訓練營練習比賽(第五場)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;
}