[GYM102979C]Colorful Squares

newcommand 写的 LaTeX 起手式看不到就是了...

壹、题目描述 ¶

传送门 to CF.

贰、题解 ¶

先二分答案 s 转判定性问题。考虑扫描线,对于大小为 s 的正方形,先把 x=x0 的所有点加入集合,再在 x=x0+s+1 的时候把 x=x0 加入的点从集合中删除。那么现在就是判断集合中是否存在一个 y,使得 [ys,y] 里面有所有的 k 种颜色。

设有一个数据结构,其每个点 i 维护 [is,i] 的颜色种数,假设我们加入点为 y0,那么它会增加该数据结构中的 [y0,y0+s] 中所有点维护区间的颜色种数,该操作即区间 +1,过,可能这其中已经有些点包含了该点的颜色 c0,故而,我们还需要对于每个颜色开一个 multiset 维护该颜色所有 y 的位置,插入 y0 时,找到前驱和后继 y1,y2,那么我们最终修改的区间就是

(max{y1+s+1,y0},min{y21,y0+s})

最后统计时,只需查看是否存在某个位置维护的值为 k 即可,这和区间最大值差不过。撤销操作,其实就是区间减 1.

关于数据结构的选择,显然我们可以使用线段树做到。时间复杂度 O(nlogxloga).

叁、参考代码 ¶

# include <cstdio>
# include <algorithm>
# include <cstring>
# include <vector>
# include <set>
using namespace std;

// # define NDEBUG
# include <cassert>

namespace Elaina {

# define rep(i, l, r) for(int i=(l), i##_end_=(r); i<=i##_end_; ++i)
# define drep(i, l, r) for(int i=(l), i##_end_=(r); i>=i##_end_; --i)
# define fi first
# define se second
# define mp(a, b) make_pair(a, b)
# define Endl putchar('\n')
# define mmset(a, b) memset(a, b, sizeof (a))
# define mmcpy(a, b) memcpy(a, b, sizeof (a))

typedef long long ll;
typedef unsigned long long ull;
typedef pair <int, int> pii;
typedef pair <ll, ll> pll;

template <class T> inline T fab(T x) { return x<0? -x: x; }
template <class T> inline void getmin(T& x, const T rhs) { x=min(x, rhs); }
template <class T> inline void getmax(T& x, const T rhs) { x=max(x, rhs); }
template <class T> inline T readin(T x) {
    x=0; int f=0; char c;
    while((c=getchar())<'0' || '9'<c) if(c=='-') f=1;
    for(x=(c^48); '0'<=(c=getchar()) && c<='9'; x=(x<<1)+(x<<3)+(c^48));
    return f? -x: x;
}

template <class T> inline void writc(T x, char s='\n') {
    static int fwri_sta[1005], fwri_ed=0;
    if(x<0) putchar('-'), x=-x;
    do fwri_sta[++fwri_ed]=x%10, x/=10; while(x);
    while(putchar(fwri_sta[fwri_ed--]^48), fwri_ed);
    putchar(s);
}

} using namespace Elaina;

const int maxn=2e5;
const int maxx=2.5e5;
const int inf=0x3f3f3f3f;

int n, k;
vector<pii>v[maxx+5];

inline void input() {
    n=readin(1), k=readin(1);
    int x, y, c;
    rep(i, 1, n) {
        x=readin(1), y=readin(1), c=readin(1);
        v[x].push_back({y, c});
    }
}

namespace saya {

int mx[maxx<<2|2];
int tag[maxx<<2|2]; // modify tag
bool cls[maxx<<2|2]; // clear tag

# define ls (i<<1)
# define rs (i<<1|1)
# define mid ((l+r)>>1)
# define _lhs ls, l, mid
# define _rhs rs, mid+1, r

inline void clear(int i) {
    mx[i]=tag[i]=0, cls[i]=1;
}
inline void add(int i, int val) {
    mx[i]+=val, tag[i]+=val;
}
inline void pushdown(int i) {
    if(!cls[i] && !tag[i]) return;
    if(cls[i]) clear(ls), clear(rs), cls[i]=0;
    if(tag[i]) add(ls, tag[i]), add(rs, tag[i]), tag[i]=0;
}
inline void pushup(int i) {
    mx[i]=max(mx[ls], mx[rs]);
}
void modify(int L, int R, int val, int i=1, int l=1, int r=maxx) {
    // if(i==1) printf("modify :> L == %d, R == %d, val == %d\n", L, R, val);
    if(L>R || r<L || l>R) return;
    if(L<=l && r<=R) return add(i, val);
    pushdown(i);
    if(L<=mid) modify(L, R, val, _lhs);
    if(mid<R) modify(L, R, val, _rhs);
    pushup(i);
}

# undef ls
# undef rs
# undef mid
# undef _lhs
# undef _rhs

}

multiset<int>c[maxn+5];
inline bool check(int s) {
    /** initialization */
    saya::clear(1);
    rep(i, 1, k) {
        c[i].clear();
        c[i].insert(-inf), c[i].insert(inf);
    }
    rep(x, 1, maxx) {
        /** add part */
        for(auto [y, col]: v[x]) {
            c[col].insert(y);
            auto it=c[col].find(y);
            auto pre=it, suf=it; --pre, ++suf;
            saya::modify(max(*pre+s+1, y), min(*suf-1, y+s), 1);
        }
        /** remove part */
        if(x-s-1>=1) {
            for(auto [y, col]: v[x-s-1]) {
                auto it=c[col].find(y);
                assert(it!=c[col].end()); // it should have found
                auto pre=it, suf=it; --pre, ++suf;
                saya::modify(max(*pre+s+1, y), min(*suf-1, y+s), -1);
                c[col].erase(it); // should erase the iterator
            }
        }
        // printf("When x == %d, mx == %d\n", x, saya::mx[1]);
        if(saya::mx[1]==k) return true;
    }
    return false;
}

signed main() {
    input();
    int l=0, r=maxx, mid, ans=-1;
    while(l<=r) {
        mid=(l+r)>>1;
        // printf("Now l == %d, r == %d, mid == %d\n", l, r, mid);
        if(check(mid)) ans=mid, r=mid-1;
        else l=mid+1;
    } writc(ans);
    return 0;
}

肆、关键之处 ¶

好像之前做机器人那道傻逼树套树也用过这种思路,但是当时没调出来就忽略了这种思路,现在想想居然还挺巧妙,与其说是扫描线,我倒觉得更像是动态地维护一个队列以及其中的点的信息吧?不过说成是两根扫描线也挺形象。

posted @   Arextre  阅读(141)  评论(1编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示