【YBT2022寒假Day8 B】【luogu CF603E】奇度边集 / Pastoral Oddities(结论)(cdq分治)(可撤回并查集)

2022/2/15 23:12:02

本文主要是介绍【YBT2022寒假Day8 B】【luogu CF603E】奇度边集 / Pastoral Oddities(结论)(cdq分治)(可撤回并查集),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

奇度边集 / Pastoral Oddities

题目链接:YBT2022寒假Day8 B / luogu CF603E

题目大意

给你一个 n 个点的图,然后一开始没有边,依次加边,然后每次问你当前是否存在一个边集,使得所有点度数都是奇数。
如果存在输出选的边权的最大边权的最小值,如果不存在输出 -1。

思路

首先我们要考虑存在点集所有点度数都是奇数的边需要怎样。
先说结论:需要不存在奇数个点的连通块。

首先证明奇数的肯定不行,不难看到那奇数个点,每个都是奇数个度数,那总度数就是奇数,但是一条边提供两个度数,总度数肯定是偶数,所以肯定不行。
然后证明偶数的行,我们可以对它做树形 DP,从根往上,连接没有连接的,已经连接的就不选它跟父亲的边,不难看出一定可以匹配完。

然后接着考虑怎么最小化边集的边权,不难想到一个类似最小生成树的算法。

然后这时候其实是可以用 LCT 来搞,就每次加边不断尝试删最小生成树中边权最大的边(用堆维护这些边),如果删到不合法了,那最后的边就是答案(也不能删了)。

但是这里我们也可以用 cdq 分治来做。
我们考虑把边按权值排序,然后我们设当前询问的区间(这里按插入时间排)为 \([l,r]\),答案区间为 \([L,R]\)(这里是按权值排序的顺序)。

然后就是对于一个位置 \(mid\) 求它的答案。
我们考虑用并查集维护,那首先我们要把 \([l,mid]\) 中在 \([1,L-1]\) 的边给放进去。

然后我们看看在 \([L,R]\) 中的边,如果它的位置在 \([1\sim mid]\) 就放进去,如果发现没有没有奇数连通块了,那就是当时的位置对于边的权值。
然后我们假设求出来的答案是 \(ans\)。

那显然我们要处理一些东西:
在处理 \([mid+1,r]\) 之前,我们要处理 \([l,mid]\) 中在 \([1,L-1]\) 的部分。
在处理 \([l,mid-1]\) 之前,我们要处理 \([L,ans]\) 权值之间的 \([1\sim l-1]\) 时间出现的给加入。

然后两边是分开的,所以我们要用的是可撤回并查集。

代码

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

using namespace std;

struct node {
	int x, y, w, pl;
}a[300001], b[300001];
int n, m, pl[300001];
int ans[300001];

bool cmp(node x, node y) {
	return x.w < y.w;
}

struct Heap {
	int fa[300001], sz[300001], tot, sta[300001];
	
	void Init() {
		tot = n; for (int i = 1; i <= n; i++) fa[i] = i, sz[i] = 1;
	}
	
	int find(int now) {
		if (fa[now] == now) return now;
		return find(fa[now]);
	}
	
	void connect(int x, int y) {
		int X = find(x), Y = find(y);
		if (X == Y) return ;
		if (sz[X] > sz[Y]) swap(X, Y), swap(x, y);
		fa[X] = Y; tot -= (sz[X] & 1) + (sz[Y] & 1);
		sz[Y] += sz[X]; tot += (sz[Y] & 1);
		sta[++sta[0]] = X;
	}
	
	void back() {
		int X = sta[sta[0]], Y = fa[X];
		tot -= (sz[Y] & 1); sz[Y] -= sz[X];
		tot += (sz[X] & 1) + (sz[Y] & 1); fa[X] = X;
		sta[0]--;
	}
}H;

void clac(int mid, int l, int r, int L, int R) {
	int lst = H.sta[0];
	
	for (int i = l; i <= mid; i++)
		if (pl[i] < L) H.connect(b[i].x, b[i].y);
	for (int i = L; i <= R; i++) {
		if (a[i].pl <= mid) {
			H.connect(a[i].x, a[i].y);
			if (!H.tot) {
				ans[mid] = i; break;
			}
		}
	}
	
	while (H.sta[0] > lst) H.back();
}

void cdq(int l, int r, int L, int R) {
	if (l > r) return ;
	int mid = (l + r) >> 1;
	clac(mid, l, r, L, R);
	
	if (!ans[mid]) {
		for (int i = l; i <= mid; i++)
			if (pl[i] < L) H.connect(b[i].x, b[i].y);
		cdq(mid + 1, r, L, R);
		return ;
	}
	
	int lst = H.sta[0];
	for (int i = L; i <= ans[mid]; i++)
		if (a[i].pl < l) H.connect(a[i].x, a[i].y);
	cdq(l, mid - 1, ans[mid], R);
	while (H.sta[0] > lst) H.back();
	
	lst = H.sta[0];
	for (int i = l; i <= mid; i++)
		if (pl[i] < L) H.connect(b[i].x, b[i].y);
	cdq(mid + 1, r, L, ans[mid]);
	while (H.sta[0] > lst) H.back();
}

int main() {
	scanf("%d %d", &n, &m);
	for (int i = 1; i <= m; i++) {
		scanf("%d %d %d", &a[i].x, &a[i].y, &a[i].w); a[i].pl = i;
	}
	memcpy(b, a, sizeof(b)); H.Init();
	sort(a + 1, a + m + 1, cmp);
	
	for (int i = 1; i <= m; i++)
		pl[a[i].pl] = i;
	cdq(1, m, 1, m);
	
	for (int i = 1; i <= m; i++)
		printf("%d\n", ans[i] ? a[ans[i]].w : -1);
	
	return 0;
}


这篇关于【YBT2022寒假Day8 B】【luogu CF603E】奇度边集 / Pastoral Oddities(结论)(cdq分治)(可撤回并查集)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程