【luogu U137971】公司搬迁(图论模型)(dfs / 并查集)

86 篇文章 1 订阅
52 篇文章 1 订阅

公司搬迁

题目链接:luogu U137971

题目大意

给你 n 个互不相同的数,然后你可以把它分成两类。
两个类分别有一个数字 a,b,如果数字 i 在第一类那 a-i 也要在第一类,如果在第二类就是 b-i。
然后问你是否有一个方法可以分类并满足条件。

思路

考虑建立一个图论模型。

如果 i i i 能和 j j j 匹配(不管是在第一类还是第二类),就连边。

然后不难发现由于只有两类,所以一个点只有至多两条边。而且由于是两个不同的类,你只能用一条。

那我们就会出现一些连通块,如果这个连通块的数量是偶数,那我们可以把它两两组合,就是可以的。但如何出现奇数,那无论怎么分,都是会剩一个,所以就不行

吗?

其实会有特殊情况,就是一个点连向自己,那它既可以单独匹配,也可以跟别人匹配。
而且由于上面一个点至多两条边,它一定是在一个链的两端。那就算既是奇数的,有了这些“自环点”,也可以匹配。

找连通块可以那并查集来维护,也可以直接建图搜索。

然后再主要以判一下有没有点两种都匹配不了,那直接输出 NO 就行了。


其实还有两种方法,但这里就不写了,口胡一下就好了。

一种就是 2-set,你看一下会发现确实是这样的。

一种就是普通的并查集,然后什么把边分成 A,B 边,然后搞并查集。
如果 x x x 个第一个集合可以,那 x , a − x x,a-x x,ax 连边,第二个集合也一样。
然后并查集的时候你先把每个点确定它是否可以用第一个或第二个集合连边,然后合并的时候要去与的结果。
(就是要找到的连通块可以都是 A 或者可以都是 B)

代码

#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;

struct node {
	int to, nxt;
}e[200001];
int T, p[100001], n, a, b;
int le[100001], KK, allnum, klnum;
bool yes, workkk, kl[100001];
bool in[100001];

void clean() {
	memset(le, 0, sizeof(le));
	memset(kl, 0, sizeof(kl));
	memset(in, 0, sizeof(in));
}

void add(int x, int y) {
	e[++KK] = (node){y, le[x]}; le[x] = KK;
	e[++KK] = (node){x, le[y]}; le[y] = KK;
}

void work(int now) {//dfs 找连通块点个数和自环点个数
	in[now] = 1;
	allnum++;
	if (kl[now]) klnum++;
	for (int i = le[now]; i; i = e[i].nxt)
		if (!in[e[i].to]) {
			work(e[i].to);
		}
}

int main() {
	scanf("%d", &T);
	while (T--) {
		scanf("%d %d %d", &n, &a, &b);
		for (int i = 1; i <= n; i++) scanf("%d", &p[i]);
		
		sort(p + 1, p + n + 1);
//		n = unique(p + 1, p + n + 1) - p - 1; 
		
		workkk = 1; KK = 1;
		for (int i = 1; i <= n; i++) {
			if (a - p[i] == p[i] || b - p[i] == p[i]) {//自连边
				kl[i] = 1;
			}
			yes = 0;
			int x = lower_bound(p + 1, p + n + 1, a - p[i]) - p;
			if (p[x] == a - p[i]) {
				yes = 1;
				if (p[i] < a - p[i]) add(i, x);
			}
			x = lower_bound(p + 1, p + n + 1, b - p[i]) - p;
			if (p[x] == b - p[i]) {
				yes = 1;
				if (p[i] < b - p[i]) add(i, x);
			}
			if (!yes) {//有人不能跟任何人匹配,不行
				printf("NO\n");
				workkk = 0;
				break;
			}
		}
		
		if (!workkk) {
			clean();
			continue;
		}
		
		for (int i = 1; i <= n; i++)//连边可以用并查集,然后也是维护大小和是否有自连边
			if (!in[i]) {
				allnum = klnum = 0;
				work(i);
				if (allnum & 1)//大小是奇数而且没有自连边,就不行,有一个匹配不了
					if (!klnum) {
						printf("NO\n");
						workkk = 0;
						break;
					}
			}
		
		if (workkk) {
			printf("YES\n");
		}
		
		clean();
	}
	
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值