题解:P8389 [COI 2021] Izvanzemaljci

我声称我的做法是对的。

欢迎来 hack!

二分 \(L\),我们判断能否用至多 \(K\) 个边长 \(\leq L\) 的正方形覆盖所有点。注意这里钦定每个正方形内都有点

若仅使用一个正方形,判断是否有 \(\max(mxX-mnX,mxY-mnY)\leq L\) 即可。

对于两个不交的正方形,必然存在一个轴,使得这两个正方形在轴上对应的区间不交。如果钦定这个轴为主轴,那么对于所有主轴坐标相同的点,我们只关心次轴坐标的最小值和最大值。

若使用两个正方形,不妨钦定 \(x\) 轴为主轴。设所有不同的主轴坐标为 \(p_1<\cdots<p_m\),那么我们可以枚举 \(1\leq i<m\),判断能否用两个边长 \(\leq L\) 的正方形分别覆盖 \(x\leq p_i\)\(x\geq p_{i+1}\) 的点即可。

若使用三个正方形,考察这三个正方形两两间的不交轴,至少有两个不交轴是相同的。不妨设这个相同的不交轴是 \(x\) 轴,那么这三个正方形两两间的不交轴只有 \((x,x,y)\)\((x,x,x)\) 两种类型。

对于 \((x,x,y)\),我们发现这三个正方形一定是这样的形式:

Ciy7Z4j.md.png

此时不难发现 \(A\) 一定是越大越好。因为若存在合法方案使得 \(A\) 的右边界能够向右延伸,我们延伸出去,再平移 \(B,C\) 的左边界即可得到另一个合法方案。设所有不同的主轴坐标为 \(p_1<\cdots<p_m\),我们找出最大的 \(l\) 使得 \(A\) 能覆盖 \(x\leq p_l\) 的点,然后对 \(x>p_l\) 的点跑两个正方形的做法即可。

较为复杂的是 \((x,x,x)\)。此时三个正方形一定是这样的形式:

CiyM0CB.md.png

\(B\) 包含了所有 \(p_l\leq x\leq p_r\) 的点。考察 \(l,r\) 合法的条件:

  • 显然 \(x\leq p_{l-1}\) 的点能被一个边长 \(\leq L\) 的正方形覆盖,\(x\geq p_{r+1}\) 的点也是。
  • 而对于 \(p_l\leq x\leq p_r\) 的点,能包住它们的正方形的最小边长为 \(a=\max(p_r-p_l,d(l,r),1)\),其中 \(d(l,r)=\max\limits_{p_l\leq x_i\leq p_r}y_i-\min\limits_{p_l\leq x_i\leq p_r}y_i\)。显然要满足 \(a\leq L\)
  • 考虑 \(B\) 能取到的最靠左的左边界为 \(\max(p_{l-1}+1,p_r-a)\),于是还要满足 \(\max(p_{l-1}+1,p_r-a)+a\leq p_{r+1}-1\)

\(r\) 从小到大扫描线。显然第一个条件是容易维护的,第二个条件双指针加上单调队列也很容易维护。

对于第三个条件,显然我们只用判断是否有 \(p_{l-1}+1+a\leq p_{r+1}-1\)。而观察到 \(p_{l-1}+1+p_r-p_l\leq p_r\leq p_{r+1}-1\),因此实际上只需要判断是否有 \(p_{l-1}+1+\max(d(l,r),1)\leq p_{r+1}-1\)。使用线段树加单调栈容易维护出 \(p_{l-1}+d(l,r)\),而前面两个条件会把 \(l\) 限制在一个区间内,所以可以直接查询区间最小值,为了构造方案还需要记录最小值位置。这样没有考虑到对 \(1\)\(\max\),注意到 \(d(l,r)=0\)\(l\) 是一段后缀 \([s,r]\),不难在扫 \(r\) 的同时维护这个 \(s\)。于是我们只需要分成两段区间来查询即可。

于是我们就得到了 \(\mathcal{O}(n\log{n})\) 的 check。

判定过程中记录方案类型和分界线即可得到构造。可以先通过分界线将点进行分组,然后对于同一组内的点,先求出包含该组内点的最小正方形,然后把这个正方形根据方案类型贴到四个角之一。这里要注意细节:对于 \((x,x,x)\) 类型的中间的正方形,直接贴到最左侧可能是错的,还需要考虑前面的正方形的右边界。\((y,y,y)\) 类型同理。

注意到我们二分时判定的是用至多 \(K\) 个边长 \(\leq L\) 的正方形能否覆盖所有点。所以构造方案时,若正方形个数不足 \(K\),还需要在 \(3\times 10^9\) 的边界处补充若干个正方形。

实现起来有很多细节,可以结合代码感受。适度封装会写起来舒服很多。

代码
#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 = 1e5 + 5;
const ll inf = 1e18;

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 n, k;
pair<ll, ll> pt[MAXN];
vector<int> ord[2];

int top1, stk1[MAXN];
int top2, stk2[MAXN];
int hd1, tl1, Q1[MAXN];
int hd2, tl2, Q2[MAXN];

struct Seg {
	ll x, l, r;
};

vector<Seg> vec[2];

int X(int p, int flag) {
	return !flag ? pt[p].first : pt[p].second;
}

int Y(int p, int flag) {
	return !flag ? pt[p].second : pt[p].first;
}

struct SegTree {
#define ls(p) (p << 1)
#define rs(p) (p << 1 | 1)
	struct Node {
		ll mn;
		int pos;

		friend Node operator+(const Node &lhs, const Node &rhs) {
			return lhs.mn <= rhs.mn ? lhs : rhs;
		}

		Node &operator+=(const Node &rhs) {
			return *this = *this + rhs;
		}
	} nd[MAXN << 2];

	ll tg[MAXN << 2];

	void pushUp(int p) {
		nd[p] = nd[ls(p)] + nd[rs(p)];
	}

	void apply(int p, ll v) {
		tg[p] += v;
		nd[p].mn += v;
	}

	void pushDown(int p) {
		if (!tg[p]) return;
		apply(ls(p), tg[p]);
		apply(rs(p), tg[p]);
		tg[p] = 0;
	}

	void build(int p, int l, int r) {
		tg[p] = 0;
		nd[p] = {inf, l};
		if (l == r) return;
		int mid = l + r >> 1;
		build(ls(p), l, mid);
		build(rs(p), mid + 1, r);
	}

	void upd(int p, int l, int r, int x, ll v) {
		if (l == r) {
			nd[p].mn = v;
			return;
		}
		pushDown(p);
		int mid = l + r >> 1;
		if (x <= mid) upd(ls(p), l, mid, x, v);
		else upd(rs(p), mid + 1, r, x, v);
		pushUp(p);
	}

	void add(int p, int l, int r, int x, int y, ll v) {
		if (x <= l && y >= r) {
			apply(p, v);
			return;
		}
		pushDown(p);
		int mid = l + r >> 1;
		if (x <= mid) add(ls(p), l, mid, x, y, v);
		if (y > mid) add(rs(p), mid + 1, r, x, y, v);
		pushUp(p);
	}

	Node query(int p, int l, int r, int x, int y) {
		if (x > y) return {inf, -1};
		if (x <= l && y >= r) return nd[p];
		pushDown(p);
		int mid = l + r >> 1;
		Node res = {inf, -1};
		if (x <= mid) res = query(ls(p), l, mid, x, y);
		if (y > mid) res += query(rs(p), mid + 1, r, x, y);
		return res;
	}
#undef ls
#undef rs
} sgt;

vector<Seg> comp(int flag, int tp = 0, int v = 0) {
	// tp = 1: X > v
	// tp = 2: X < v
	// tp = 3: Y > v
	// tp = 4: Y < v
	vector<Seg> res;
	for (int i : ord[flag]) {
		int x = X(i, flag), y = Y(i, flag);
		if (tp == 1 && y <= v) continue;
		if (tp == 2 && y >= v) continue;
		if (res.empty() || res.back().x != x) res.push_back({x, y, y});
		else res.back().r = y;
	}
	return res;
}

int getPre(const vector<Seg> &vec, int L) {
	if (vec.empty()) return -1;
	int res = -1;
	ll mnY = inf, mxY = -inf;
	for (int i = 0; i < vec.size(); ++i) {
		auto [x, l, r] = vec[i];
		chkMin(mnY, l);
		chkMax(mxY, r);
		if (x - vec[0].x <= L && mxY - mnY <= L) res = i;
		else break;
	}
	return res;
}

int getSuf(const vector<Seg> &vec, int L) {
	if (vec.empty()) return 0;
	int res = vec.size();
	ll mnY = inf, mxY = -inf;
	for (int i = vec.size() - 1; i >= 0; --i) {
		auto [x, l, r] = vec[i];
		chkMin(mnY, l);
		chkMax(mxY, r);
		if (vec.back().x - x <= L && mxY - mnY <= L) res = i;
		else break;
	}
	return res;
}

bool one(ll L) {
	ll mnX = inf, mxX = -inf, mnY = inf, mxY = -inf;
	for (int i = 0; i < n; ++i) {
		auto [x, y] = pt[i];
		chkMin(mnX, x);
		chkMax(mxX, x);
		chkMin(mnY, y);
		chkMax(mxY, y);
	}
	return max(mxX - mnX, mxY - mnY) <= L;
}

pair<bool, ll> two(const vector<Seg> &vec, int L) {
	if (vec.size() < 2) return {false, 0};
	int l = max(getSuf(vec, L) - 1, 0), r = min(getPre(vec, L), (int)vec.size() - 2);
	return {l <= r, vec[l].x};
}

tuple<bool, ll, ll> three(const vector<Seg> &vec, int L) {
	int sz = vec.size();
	if (sz < 3) return {false, 0, 0};

	int pre = getPre(vec, L);
	if (pre == -1) return {false, 0, 0};

	vector<int> suf(sz);
	ll mnY = inf, mxY = -inf;
	for (int i = sz - 1; i >= 0; --i) {
		auto [x, l, r] = vec[i];
		chkMin(mnY, l);
		chkMax(mxY, r);
		suf[i] = vec.back().x - x <= L && mxY - mnY <= L;
	}

	sgt.build(1, 0, sz - 1);
	stk1[top1 = 0] = stk2[top2 = 0] = -1;
	hd1 = hd2 = 1;
	tl1 = tl2 = 0;

	int pt1 = 0, pt2 = 1;
	for (int r = 0; r < sz; ++r) {
		while (top1 && vec[stk1[top1]].r <= vec[r].r) {
			sgt.add(1, 0, sz - 1, stk1[top1 - 1] + 1, stk1[top1], vec[r].r - vec[stk1[top1]].r);
			--top1;
		}
		stk1[++top1] = r;

		while (top2 && vec[stk2[top2]].l >= vec[r].l) {
			sgt.add(1, 0, sz - 1, stk2[top2 - 1] + 1, stk2[top2], vec[stk2[top2]].l - vec[r].l);
			--top2;
		}
		stk2[++top2] = r;

		sgt.upd(1, 0, sz - 1, r, (r ? vec[r - 1].x : 0) + vec[r].r - vec[r].l);

		while (hd1 <= tl1 && vec[Q1[tl1]].r <= vec[r].r) --tl1;
		Q1[++tl1] = r;
		while (hd2 <= tl2 && vec[Q2[tl2]].l >= vec[r].l) --tl2;
		Q2[++tl2] = r;

		while (pt1 <= r && (vec[r].x - vec[pt1].x > L || vec[Q1[hd1]].r - vec[Q2[hd2]].l > L)) {
			if (Q1[hd1] == pt1) ++hd1;
			if (Q2[hd2] == pt1) ++hd2;
			++pt1;
		}

		if (vec[r].l != vec[r].r) pt2 = r + 1;
		else if (!r || vec[r - 1].l != vec[r - 1].r || vec[r - 1].l != vec[r].l) pt2 = r;

		if (!r || r == sz - 1 || !suf[r + 1]) continue;

		int l1 = max(pt1, 1), r1 = min(pre + 1, r);
		if (l1 > r1) continue;
		auto res1 = sgt.query(1, 0, sz - 1, l1, min(pt2 - 1, r1));
		auto res2 = sgt.query(1, 0, sz - 1, max(l1, pt2), r1);
		++res2.mn;
		auto res = res1 + res2;
		if (res.mn + 1 < vec[r + 1].x) return {true, vec[res.pos - 1].x, vec[r].x};
	}
	return {false, 0, 0};
}

pair<bool, tuple<int, ll, ll>> check(int L) {
	if (one(L)) return {true, {0, 0, 0}};

	if (k >= 2){
		auto [ok1, x1] = two(vec[0], L);
		if (ok1) return {true, {1, x1, 0}};
		auto [ok2, x2] = two(vec[1], L);
		if (ok2) return {true, {2, x2, 0}};
	}
	
	if (k == 3) {
		int p = getPre(vec[0], L);
		if (p != -1) {
			auto [ok3, x3] = two(comp(1, 1, vec[0][p].x), L);
			if (ok3) return {true, {3, vec[0][p].x, x3}};
		}

		p = getSuf(vec[0], L);
		if (p != vec[0].size()) {
			auto [ok4, x4] = two(comp(1, 2, vec[0][p].x), L);
			if (ok4) return {true, {4, vec[0][p].x, x4}};
		}

		p = getPre(vec[1], L);
		if (p != -1) {
			auto [ok5, x5] = two(comp(0, 1, vec[1][p].x), L);
			if (ok5) return {true, {5, vec[1][p].x, x5}};
		}

		p = getSuf(vec[1], L);
		if (p != vec[1].size()) {
			auto [ok6, x6] = two(comp(0, 2, vec[1][p].x), L);
			if (ok6) return {true, {6, vec[1][p].x, x6}};
		}

		auto [ok7, a7, b7] = three(vec[0], L);
		if (ok7) return {true, {7, a7, b7}};
		auto [ok8, a8, b8] = three(vec[1], L);
		if (ok8) return {true, {8, a8, b8}};
	}

	return {false, {0, 0, 0}};
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	
	cin >> n >> k;
	for (int i = 0; i < n; ++i) {
		int x, y;
		cin >> x >> y;
		pt[i] = {x, y};
	}

	for (int flag : {0, 1}) {
		ord[flag].resize(n);
		iota(ord[flag].begin(), ord[flag].end(), 0);
		sort(ord[flag].begin(), ord[flag].end(), [&](int lhs, int rhs) {
			return X(lhs, flag) != X(rhs, flag) ? X(lhs, flag) < X(rhs, flag) : Y(lhs, flag) < Y(rhs, flag);
		});
		vec[flag] = comp(flag);
	}

	ll l = 1, r = 2e9;
	tuple<int, ll, ll> ans;
	auto [ok1, res1] = check(r);
	if (ok1) ans = res1;
	while (l < r) {
		ll mid = l + r >> 1;
		auto [ok, res] = check(mid);
		if (ok) {
			r = mid;
			ans = res;
		} else {
			l = mid + 1;
		}
	}

	auto [tp, a, b] = ans;
	int nk = !tp ? 1 : (tp <= 2 ? 2 : 3);
	vector<ll> mnX(nk, inf), mxX(nk, -inf), mnY(nk, inf), mxY(nk, -inf);
	for (int i = 0; i < n; ++i) {
		auto [x, y] = pt[i];
		int c;
		if (!tp) c = 0;
		else if (tp == 1) c = x > a;
		else if (tp == 2) c = y > a;
		else if (tp == 3) c = x <= a ? 0 : (y <= b ? 1 : 2);
		else if (tp == 4) c = x >= a ? 0 : (y <= b ? 1 : 2);
		else if (tp == 5) c = y <= a ? 0 : (x <= b ? 1 : 2);
		else if (tp == 6) c = y >= a ? 0 : (x <= b ? 1 : 2);
		else if (tp == 7) c = x <= a ? 0 : (x <= b ? 1 : 2);
		else c = y <= a ? 0 : (y <= b ? 1 : 2);
		
		chkMin(mnX[c], x);
		chkMax(mxX[c], x);
		chkMin(mnY[c], y);
		chkMax(mxY[c], y);
	}

	// 0: (mnX, mnY)
	// 1: (mxX, mnY)
	// 2: (mnX, mxY)
	// 3: (mxX, mxY)
	vector<int> ctp;
	if (!tp) ctp = {0};
	else if (tp == 1) ctp = {1, 0};
	else if (tp == 2) ctp = {2, 0};
	else if (tp == 3) ctp = {1, 2, 0};
	else if (tp == 4) ctp = {0, 3, 1};
	else if (tp == 5) ctp = {2, 1, 0};
	else if (tp == 6) ctp = {0, 3, 2};
	else if (tp == 7) ctp = {1, 1, 0};
	else ctp = {2, 2, 0};

	for (int i = 0; i < nk; ++i) {
		ll L = max({mxX[i] - mnX[i], mxY[i] - mnY[i], 1ll});
		if (!ctp[i]) {
			cout << mnX[i] << ' ' << mnY[i] << ' ' << L << '\n';
		} else if (ctp[i] == 1) {
			ll x = mxX[i] - L;
			if (tp == 7 && i == 1) chkMax(x, mxX[0] + 1);
			cout << x << ' ' << mnY[i] << ' ' << L << '\n';
		} else if (ctp[i] == 2) {
			ll y = mxY[i] - L;
			if (tp == 8 && i == 1) chkMax(y, mxY[0] + 1);
			cout << mnX[i] << ' ' << y << ' ' << L << '\n';
		} else {
			cout << mxX[i] - L << ' ' << mxY[i] - L << ' ' << L << '\n';
		}
	}

	ll f = 3e9;
	if (nk < k) cout << f << ' ' << f << ' ' << 1 << '\n';
	if (nk + 1 < k) cout << f << ' ' << -f << ' ' << 1 << '\n';
	return 0;
}
posted @ 2026-08-16 23:12  P2441M  阅读(5)  评论(0)    收藏  举报