[GYM102979C]Colorful Squares

\[\text{newcommand 写的 LaTeX 起手式看不到就是了...} \newcommand\bra[1]{\left({#1}\right)} \newcommand\Bra[1]{\left\{{#1}\right\}} \newcommand\dx[0]{\text{dx}} \newcommand\string[2]{\genfrac{\{}{\}}{0pt}{}{#1}{#2}} \newcommand\down[2]{{#1}^{\underline{#2}}} \newcommand\ddiv[2]{\left\lfloor\frac{#1}{#2}\right\rfloor} \newcommand\udiv[2]{\left\lceil\frac{#1}{#2}\right\rceil} \newcommand\lcm[0]{\operatorname{lcm}} \newcommand\set[1]{\left\{{#1}\right\}} \]

壹、题目描述 ¶

传送门 to CF.

贰、题解 ¶

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

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

\[\bra{\max\set{y_1+s+1,y_0},\min\set{y_2-1,y_0+s}} \]

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

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

叁、参考代码 ¶

# 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 @ 2021-08-31 10:18  Arextre  阅读(169)  评论(1)    收藏  举报