CF603E Pastoral Oddities

CF603E Pastoral Oddities

存在这样满足每个点的度数均为奇数的边集,充分条件是连通块的点数为偶数。

先考虑必要性,加入一边度数和增加 2 2 2 故度数和为偶数,而当满足条件的连通块点数为奇时,度数和必为奇数,矛盾。

证明充分性:对于一个偶数大小的连通块,找出其一棵生成树,从叶子开始根据连向儿子的度数考虑是否添加其到父亲的边,逐步满足,最后除了根之外都满足条件,显然根据奇偶性根也满足条件,故充分性保证。

故用一个按秩合并并查集即可维护连通块是否满足条件。

再考虑边集中的最大边权最小的限制,一棵最小生成树足矣。

在线 LCT 维护最小生成树森林,时间复杂度 O ( n log ⁡ n ) \mathcal O(n\log n) O(nlogn)

但非常难写且常数巨大,考虑离线做法。

观察样例得知,时间升序,答案单调递减;还有“最大值最小”一类的词,考虑二分。

多次询问,考虑整体二分。

具体地,离散化权值,再对权值分治,令其为 m i d mid mid

添加权值 ≤ m i d \leq mid mid 的边直到合法,根据第一个合法的位置分治。

注意细节,整体二分后面的状态 ( l , r , v l , v r ) (l,r,vl,vr) (l,r,vl,vr) 需要并查集中含有所有编号 ≤ l \leq l l 且权值 ≤ v l \leq vl vl 的边,考虑维护一个可撤销并查集即可。

时间复杂度 O ( m log ⁡ m log ⁡ n ) \mathcal O(m\log m\log n) O(mlogmlogn)

#include<cstdio>
#include<algorithm>

typedef long long ll;

#define ha putchar(' ')
#define he putchar('\n')

inline int read() {
	int x = 0;
	char c = getchar();
	while (c < '0' || c > '9')
		c = getchar();
	while (c >= '0' && c <= '9')
		x = (x << 3) + (x << 1) + (c ^ 48), c = getchar();
	return x;
}

inline void write(int x) {
	if (x < 0) {
		putchar('-');
		x = -x;
	}
	if (x > 9)
		write(x / 10);
	putchar(x % 10 + 48);
}

const int _ = 3e5 + 5;

int n, m, cnt, fa[_], sz[_], t, s[_], ans[_];

struct abc {
	int u, v, w, id;
	bool operator < (const abc &t) {
		return w < t.w;
	}
} e[_], b[_];

int find(int x) {
	return fa[x] == x ? x : find(fa[x]);
}

void merge(int u, int v) {
	u = find(u), v = find(v);
	if (u == v) return;
	if (sz[u] < sz[v]) std::swap(u, v);
	if ((sz[u] & 1) && (sz[v] & 1)) cnt -= 2;
	sz[u] += sz[v], fa[v] = u, s[++t] = v;
}

void ers(int lim) {
	while (t > lim) {
		int v = s[t--], u = fa[v];
		fa[v] = v, sz[u] -= sz[v];
		if ((sz[u] & 1) && (sz[v] & 1)) cnt += 2;
	}
}

void solve(int l, int r, int vl, int vr) {
	if (l > r || vl > vr) return;
	if (vl == vr) {
		for (int i = l; i <= r; ++i) ans[i] = b[vl].w;
		return;
	}
	int mid = (vl + vr) >> 1, nw = t;
	for (int i = vl; i <= mid; ++i)
		if (b[i].id < l) merge(b[i].u, b[i].v);
	int Id = r + 1, nw2 = t;
	for (int i = l; i <= r; ++i) {
		if (e[i].w <= b[mid].w) merge(e[i].u, e[i].v);
		if (!cnt) {
			Id = i;
			break;
		}
	}
	ers(nw2);
	solve(l, Id - 1, mid + 1, vr);
	ers(nw);
	for (int i = l; i < Id; ++i)
		if (e[i].w <= b[vl].w) merge(e[i].u, e[i].v);
	solve(Id, r, vl, mid);
	ers(nw);
}

signed main() {
	n = cnt = read(), m = read();
	for (int i = 1; i <= n; ++i) fa[i] = i, sz[i] = 1;
	for (int i = 1; i <= m; ++i) {
		e[i].u = read(), e[i].v = read(), e[i].w = read();
		e[i].id = i, b[i] = e[i];
	}
	std::sort(b + 1, b + m + 1);
	b[m + 1].w = -1;
	solve(1, m, 1, m + 1);
	for (int i = 1; i <= m; ++i) write(ans[i]), he;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值