Colorful Squares(二分+线段树)
题意: 平面内有 n n n 个点,每个点都有颜色,总共有 k k k 中颜色,问,一个能将 k k k 种颜色的点都包含进的正方形的最小边长是多少。(边长可为 0 0 0,即只有一种颜色的情况 )
思路:
先将所有点,按 x x x 轴坐标存入 v e c t o r < n o d e > v [ i ] vector<node>v[i] vector<node>v[i] 中, v [ i ] v[i] v[i] 表示, x x x 坐标为 i i i 的所有点的坐标。
二分长度,然后对长度进行 C h e c k Check Check 。
C h e c k Check Check 的过程:
m u l t i s e t < i n t > s [ i ] multiset<int> s[i] multiset<int>s[i] : s [ i ] s[i] s[i] 表示颜色为 i i i 的点的纵坐标的集合(因为是set,内部是升序的)
线段树维护的是 y y y 轴方向的点的颜色情况。
从 1 1 1 到 250000 250000 250000 遍历横坐标 x x x :
将横坐标为 x x x 的所有点进行添加:(即 v [ x ] v[x] v[x] 中的点)
当前点 n o w now now 的控制区间是 [ n o w . y − m i d , n o w . y ] [now.y - mid, now.y] [now.y−mid,now.y] ,但还需要修改,防止和相同颜色的点的区间重合。
确定当前点的颜色 c o l col col,然后 s [ c o l ] s[col] s[col] 中点的纵坐标的情况来确定真正需要修改的区间
对 x − m i d − 1 ≥ 1 x - mid - 1 \geq 1 x−mid−1≥1 的点进行删除:(即 v [ x − m i d − 1 ] v[x - mid -1] v[x−mid−1] 中的点,因为,这里的点已经超出了 m i d mid mid 长的作用范围)
当前点 n o w now now 的控制区间是 [ n o w . y − m i d , n o w . y ] [now.y - mid, now.y] [now.y−mid,now.y] ,但还需要修改,防止和相同颜色的点的区间重合。
确定当前点的颜色 c o l col col ,根据 s [ c o l ] s[col] s[col] 中点的纵坐标的情况来确定真正需要修改的区间
然后用线段树判断当前的最大值,若为 k k k ,就表示合法,直接返回,否则继续遍历
具体见代码,注释写得挺详细的
#include<iostream>
#include<cstdio>
#include<vector>
#include<queue>
#include<stack>
#include<cmath>
#include<map>
#include<set>
#include<cstring>
#include<string>
#include<algorithm>
#define fi first
#define se second
//#include<stdlib.h>
//#include <time.h>
//srand((unsigned)time(NULL));
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int INF = 0x3f3f3f3f;
using namespace std;
const int N = 250010;
const int sz = 250000;
int n, k;
#define ls (rt << 1)
#define rs ((rt << 1) | 1)
struct seg_tree{ //区间修改+求最大值
int tr[N * 4], lz[N * 4];
void build(int l, int r, int rt) {
tr[rt] = lz[rt] = 0;
if (l == r) {
return;
}
int mid = (l + r) >> 1;
build(l, mid, ls);
build(mid + 1, r, rs);
tr[rt] = max(tr[ls], tr[rs]);
}
void pushup(int rt) {
tr[rt] = max(tr[ls], tr[rs]);
}
void pushdown(int rt) {
if (lz[rt] != 0) {
tr[ls] += lz[rt];
tr[rs] += lz[rt];
lz[ls] += lz[rt];
lz[rs] += lz[rt];
lz[rt] = 0;
}
}
void modify(int s, int t, int l, int r, int rt, int v) {
if (s <= l && r <= t) {
tr[rt] += v;
lz[rt] += v;
return;
}
pushdown(rt);
int mid = (l + r) >> 1;
if (s <= mid) modify(s, t, l, mid, ls, v);
if (mid < t) modify(s, t, mid + 1, r, rs, v);
pushup(rt);
}
int query() {
return tr[1];
}
}seg;
struct node{
int x, y;
int col;
node() {}
node(int _x, int _y, int _c) {
x = _x; y = _y;
col = _c;
}
bool operator<(const node& o) const {
return x < o.x;
}
}a[N];
vector<node> v[N];//v[i]表示x坐标为i的所有点
multiset<int> s[N];//s[i]表示颜色为i的点y坐标大小,用set是方便直接升序排好,用于判断颜色i在区间上的染色情况
bool check(int mid) {
for (int i = 0; i <= N; i++) s[i].clear();
seg.build(1, N, 1); //建立线段树,维护y轴上的颜色,叶子节点表示该y坐标值上有多少种颜色了
for (int i = 1; i <= 250000; i++) { //按x轴遍历所有点,保证线段树内的点的x坐标差值在mid内
for (node now : v[i]) { //now点能覆盖的y坐标是[y - mid, y],要对区间颜色种数进行添加
//但是不能和相同颜色的其他点在这个区间重合,因此可能要对区间范围进行修改
int col = now.col; //当前点的颜色,
//接下来讨论的都是在符合x轴mid长度区间范围内的所有col色的点
auto is_exist = s[col].find(now.y);
if (is_exist != s[col].end()) { //col色的点在y轴这个位置已经存在,因此不需要再重复添加了
s[col].insert(now.y); //因为在区间[now.y - mid, now.y]上肯定已经染过col色了,不能重复染色了。
continue;
}
s[col].insert(now.y);
int L,R; //L: 第一个比当前点的y小的点,R: 第一个比当前点的y大的点;
int l, r;//真正要修改的区间的上下限
//修改下限
auto _l = s[col].lower_bound(now.y);//找到第一个大于等于now.y的col颜色点。
if (_l == s[col].begin()) {//不存在比now.y还小的点了,因此,下限就是1
l = max(1, now.y - mid);//防止下限小于1
}
else {
L = *(--_l); //第一个比now.y小的col色点的y坐标,可能会对当前点的下限产生影响
l = max(L + 1, now.y - mid); //防止染色区间重复
}
//修改上限
auto _r = s[col].upper_bound(now.y);//扎到第一个大于now.y的col色的点
if (_r == s[col].end()) r = now.y;//不存在比now.y还大的col色的点了,不会存在点来干扰now点的上限。
else { //now点的上限会被_r点的区间干扰
R = *_r;
r = min(now.y, R - mid - 1); //防止染色区间重复
}
//区间合法,就进行修改
if (l <= r) seg.modify(l ,r, 1, N, 1, 1);
}
if (i - mid - 1 >= 1) { //x轴上,前面的 在x轴上的作用范围 超过mid的点
for (node now : v[i - mid - 1]) { //对超出作用范围的点要进行删除
int col = now.col;
s[col].erase(s[col].find(now.y));
auto is_exist = s[col].find(now.y);
if (is_exist != s[col].end()) continue; //删掉这个点后还存在这个高度的col色点,所以不需要对线段树操作
int L,R,l,r;//意义和添加时相同,修改操作同上
//修改下限,方法同上
auto _l = s[col].lower_bound(now.y);
if (_l == s[col].begin()) {
l = max(1, now.y - mid);
}
else {
L = *(--_l);
l = max(L + 1, now.y - mid);
}
//修改上限,方法同上
auto _r = s[col].upper_bound(now.y);
if (_r == s[col].end()) r = now.y;
else {
R = *_r;
r = min(now.y, R - mid - 1);
}
//区间合法就进行修改
if (l <= r) seg.modify(l, r, 1, N, 1, -1);
}
}
if (seg.query() == k) return true;//该长度的正方形范围内有
}
return false;
}
int main() {
scanf("%d%d", &n, &k);
for (int i = 1; i <= n; i++) {
scanf("%d%d%d", &a[i].x, &a[i].y, &a[i].col);
v[a[i].x].push_back(a[i]);
}
int l = 0, r = N;
while (l < r) { //二分答案,找到最小的答案
int mid = (l + r) >> 1;
if (check(mid)) r = mid;
else l = mid + 1;
}
printf("%d\n", r);
return 0;
}