公司搬迁
题目链接: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,a−x 连边,第二个集合也一样。
然后并查集的时候你先把每个点确定它是否可以用第一个或第二个集合连边,然后合并的时候要去与的结果。
(就是要找到的连通块可以都是 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;
}