题解:ARC232A Two in the Range

挺有趣味的。

赛时做法,貌似和官解有点区别。

如果把所有 \(1\) 交替染成两种颜色,每个区间恰好包含两个异色的 \(1\)。于是问题可以转化为,找出两组不相交的位置,使得每个区间在每组中恰好包含一个位置。

先考虑一组位置的限制。考察相邻的两个位置 \(x<y\),不难发现:

  • 若 \(L_i\leq x\) 则 \(y>R_i\)。
  • 若 \(L_i>x\) 则 \(y\leq R_i\)。

因此若固定 \(x\),则合法的后继位置 \(y\) 满足 \(l_x\leq y\leq r_x\),其中 \(l_x=\max\left(x,\max\limits_{L_i\leq x}R_i\right)+1\),\(r_x=\min\limits_{L_i>x}R_i\)。

首尾位置 \(p,q\) 需要满足 \(p\leq\min R_i\) 且 \(q\geq\max L_i\)。建出有向图:

  • 源点 \(S\) 向每个 \(\leq\min R_i\) 的点连边。
  • 每个点 \(x\) 向 \([l_x,r_x]\) 内的点连边。
  • 每个 \(\geq\max L_i\) 的点向汇点 \(T\) 连边。

那么问题转化为,判断能否在这个有向图上找到两条不交的 \(S\) 到 \(T\) 的路径。

这是经典问题。考虑网络流,把每个点 \(x\) 拆成 \(in_x\) 和 \(out_x\),\(in_x\) 向 \(out_x\) 连容量为 \(1\) 的边,其他边容量设为 \(2\),判断最大流是否 \(=2\) 即可。那么我们实际上可以手动模拟两轮增广,但是问题在于边数是 \(\mathcal{O}(n^2)\) 的。

先考虑第一轮增广。每个出点连接的都是一个区间内的入点,可以直接用并查集维护所有尚未入队的入点,支持查询第一个 \(\geq x\) 的未入队点和删除某个点,不断取出区间内尚未入队的入点即可。

第一轮增广成功后可以得到一条路径。对于第二轮增广:

  • 只有不在路径上的入点可以访问对应出点。
  • 在路径上的出入点可以沿着唯一一条反向边往回走。
  • 每个出点仍然可以到达区间内的入点。

类似维护即可。

时间复杂度为 \(\mathcal{O}(m+n\log{n})\) 或者 \(\mathcal{O}(m+n\alpha(n))\)。

代码
#include <bits/stdc++.h>

using namespace std;

using ll = long long;
using i128 = __int128;
using ui = unsigned int;
using ull = unsigned long long;
using u128 = unsigned __int128;
using ld = long double;
using pii = pair<int, int>;
const int MAXN = 5e5 + 5;

template<typename T> T lowbit(T x) { return x & -x; }
template<typename T> void chkMin(T &x, T y) { x = y < x ? y : x; }
template<typename T> void chkMax(T &x, T y) { x = x < y ? y : x; }
constexpr int lg2(ll x) { return 63 ^ __builtin_clzll(x); }
constexpr ll bitCeil(ll x) { return x == 1 ? 1ll : 1ll << lg2(x - 1) + 1; }

int tc, n, m, mx, mn, pre[MAXN], suf[MAXN], prv[MAXN << 1], fr[MAXN << 1];
bool vis[MAXN << 1], mark[MAXN];

struct DSU {
	int fa[MAXN];

	void init() {
		iota(fa + 1, fa + n + 2, 1);
	}

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

	void unite(int x, int y) {
		fa[find(x)] = find(y);
	}
} dsu;

int bfs() {
	fill(vis + 1, vis + n * 2 + 1, false);
	dsu.init();
	queue<int> Q;
	for (int i = 1; i <= mn; ++i) {
		Q.emplace(i);
		dsu.unite(i, i + 1);
		vis[i] = true;
	}

	while (!Q.empty()) {
		int x = Q.front();
		Q.pop();
		if (x <= n) {
			if (!mark[x] && !vis[x + n]) {
				Q.emplace(x + n);
				vis[x + n] = true;
				fr[x + n] = x;
			}
			if (prv[x] && !vis[prv[x]]) {
				Q.emplace(prv[x]);
				vis[prv[x]] = true;
			}
		} else {
			if (x - n >= mx) return x;
			if (prv[x] && !vis[prv[x]]) {
				Q.emplace(prv[x]);
				vis[prv[x]] = true;
				dsu.unite(prv[x], prv[x] + 1);
			}
			int L = max(x - n, pre[x - n]) + 1, R = suf[x - n + 1], p = dsu.find(L);
			while (p <= R) {
				Q.emplace(p);
				vis[p] = true;
				fr[p] = x;
				dsu.unite(p, p + 1);
				p = dsu.find(p);
			}
		}
	}
	return 0;
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	
	cin >> tc;
	while (tc--) {
		cin >> n >> m;

		fill(pre, pre + n + 1, 0);
		fill(suf + 1, suf + n + 2, n);
		mx = 0;
		mn = n;
		for (int i = 1; i <= m; ++i) {
			int L, R;
			cin >> L >> R;
			chkMax(pre[L], R);
			chkMin(suf[L], R);
			chkMax(mx, L);
			chkMin(mn, R);
		}
		for (int i = 1; i <= n; ++i) chkMax(pre[i], pre[i - 1]);
		for (int i = n; i; --i) chkMin(suf[i], suf[i + 1]);

		fill(prv + 1, prv + n * 2 + 1, 0);
		fill(mark + 1, mark + n + 1, false);
		fill(fr + 1, fr + n * 2 + 1, 0);
		int p = bfs();
		if (!p) {
			cout << "No\n";
			continue;
		}
		while (p) {
			if (p <= n) mark[p] = true;
			prv[p] = fr[p];
			p = fr[p];
		}

		cout << (bfs() ? "Yes" : "No") << '\n';
	}
	return 0;
}
posted @ 2026-10-11 11:35  P2441M  阅读(40)  评论(0)    收藏  举报