【ybt高效进阶 21162】双面扑克(图论模型)(线段树)(并查集)

双面扑克

题目链接:ybt高效进阶 21162

题目大意

给你 n 个牌,正面反面都有数,多次询问,每次问你能不能凑出 l~r 的顺子。

思路

考虑建立图论模型。

如果一个牌的正反面分别是 \(x,y\),就把 \(x,y\) 连一条边。
然后考虑怎样是可以凑出顺子的。

我们可以对于考虑每个数在图论模型中的连通块。
如果那个连通块不是所有点都是在顺子中,那你必然可以一条连着顺子中的数和顺子外的数选顺子内的,然后就不断的往外扩展。
那同一个道理,就算整个连通块都是顺子中的,只要它的边数大于等于它的点数,也是可以的。

那似乎很多情况都可以,那什么情况凑不出呢?
那就是这个连通块的点都是数,而且这个连通块是一棵树。
那我们可以用并查集的简单维护找到是树的连通块。

那要怎么快速判断顺子中是否包含了这些树呢?
考虑是数字,那如果这个顺子包含了这个树,这个数的点分别是 \(a_1,a_2,...,a_m\)。
那这个顺子一定有这么一个部分:\(\min\{a_1,a_2,..,a_m\}\sim\max\{a_1,a_2,...,a_n\}\)。

那我们可以把树的最大点和最小点找出来(我是直接顺手在并查集中维护),然后就变成了线段覆盖问题,离线然后线段树搞就可以啦。
(注意如果有覆盖到输出的是 No,没有才是 Yes,因为你是看是否会无法凑出)

代码

#include<cstdio>
#include<iostream>
#include<algorithm>

using namespace std;

struct tree {
	int l, r, pl;
}a[100001], q[100001];
int n, k, x, y, fa[100001], an;
int maxn[100001], minn[100001], qq;
bool ntr[100001], ans[100001];

bool cmp(tree x, tree y) {
	if (x.r != y.r) return x.r < y.r;
	return x.l < y.l;
}

int find(int now) {
	if (fa[now] == now) return now;
	return fa[now] = find(fa[now]);
}

void work(int x, int y) {//用并查集来判断是否有树
	int X = find(x), Y = find(y);
	if (X == Y) ntr[X] = 1;
	fa[X] = Y; ntr[Y] |= ntr[X];
	maxn[Y] = max(maxn[Y], maxn[X]);
	minn[Y] = min(minn[Y], minn[X]);
}

struct Tree {//离线+线段树解决线段覆盖问题
	int a[400001];
	
	void up(int now) {
		a[now] = a[now << 1] + a[now << 1 | 1]; 
	}
	
	void insert(int now, int l, int r, int pl) {
		if (l == r) {
			a[now]++;
			return ;
		}
		int mid = (l + r) >> 1;
		if (pl <= mid) insert(now << 1, l, mid, pl);
			else insert(now << 1 | 1, mid + 1, r, pl);
		up(now);
	}
	
	int query(int now, int l, int r, int L, int R) {
		if (L <= l && r <= R) return a[now];
		int mid = (l + r) >> 1, re = 0;
		if (L <= mid) re += query(now << 1, l, mid, L, R);
		if (mid < R) re += query(now << 1 | 1, mid + 1, r, L, R);
		return re;
	}
}T;

int main() {
//	freopen("yy.in", "r", stdin);
//	freopen("yy.out", "w", stdout);
	
	scanf("%d %d", &n, &k);
	
	for (int i = 1; i <= n; i++) fa[i] = maxn[i] = minn[i] = i;
	
	for (int i = 1; i <= k; i++) {
		scanf("%d %d", &x, &y);
		work(x, y);
	}
	
	for (int i = 1; i <= n; i++)
		if (!ntr[i] && find(i) == i) {
			a[++an] = (tree){minn[i], maxn[i], -1};
	}
	
	scanf("%d", &qq);
	for (int i = 1; i <= qq; i++) {
		scanf("%d %d", &q[i].l, &q[i].r);
		q[i].pl = i;
	}
	
	sort(a + 1, a + an + 1, cmp);
	sort(q + 1, q + qq + 1, cmp);
	
	int anow = 1, qnow = 1;
	for (int i = 1; i <= n; i++) {
		while (anow <= an && a[anow].r == i) {
			T.insert(1, 1, n, a[anow].l);
			anow++;
		}
		while (qnow <= qq && q[qnow].r == i) {
			if (T.query(1, 1, n, q[qnow].l, n)) ans[q[qnow].pl] = 1;
			qnow++;
		}
	}
	
	for (int i = 1; i <= qq; i++)
		if (ans[i]) printf("No\n");
			else printf("Yes\n");
	
	return 0;
} 
上一篇:树上随机游走与树上高斯消元


下一篇:生成树算法生成迷宫