Colorful Squares(二分+线段树)

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.ymid,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 xmid11 的点进行删除:(即 v [ x − m i d − 1 ] v[x - mid -1] v[xmid1] 中的点,因为,这里的点已经超出了 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.ymid,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;
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值