贪心、二分、线段树

对于二维问题有枚举一维,数据结构维护一维的套路;对于最大化最小值问题有二分答案的套路。

考虑枚举 $x$ 分割线的同时找到最优的 $y$ 分割线,这个做法似乎没有出路。

那么考虑二分答案,难点在于如何检查,考虑给定值 $mid$ 是否能成为答案。

枚举 $x$ 分割线将 $y$ 坐标划分为两个集合,对于一个集合求出最后一个合法 $y$ 分割线 $r$ 的值。具体地,设集合包含的点数为 $k$,如果 $k < mid$ 则必定不存在合法区间。

否则,找到满足集合中 $y$ 轴坐标大于等于 $Y$ 的点的数量不小于 $mid$ 的最大的整数 $Y$ 并记为 $r$,如果 $r$ 左边的点数同样不小于 $mid$ 则 $Y$ 是最小的合法分割线。在另一个集合中检查 $y$ 是否合法,如果合法那么 $mid$ 可以成为答案。因为若两个集合的合法 $y$ 分割线取值范围对应的区间有交,交集必定能取到其中一个区间的右界。

使用两棵线段树维护 $y$ 坐标对应的集合,线段树上二分求出合法分割线右界,离散化处理值域。

时间复杂度 $O(n \log ^2 n)$。

#include<bits/stdc++.h>
using namespace std;
struct SegmentTree{
    struct Segment{int l,r,w;}s[800'005];
    void build(int u,int l,int r){
        s[u].l=l,s[u].r=r,s[u].w=0;
        if(l==r) return;
        build(u*2,l,(l+r)/2);
        build(u*2+1,(l+r)/2+1,r);
    }
    void update(int u,int p,int k){
        if(s[u].l==s[u].r) return s[u].w+=k,void();
        if(p<=s[u*2].r) update(u*2,p,k);
        else update(u*2+1,p,k);
        s[u].w=s[u*2].w+s[u*2+1].w;
    }
    int query(int u,int l,int r){
        if(s[u].l>r||s[u].r<l) return 0;
        if(s[u].l>=l&&s[u].r<=r) return s[u].w;
        return query(u*2,l,r)+query(u*2+1,l,r);
    }
    int rfrank(int u,int k){
        if(s[u].l==s[u].r) return s[u].l;
        if(k<=s[u*2+1].w) return rfrank(u*2+1,k);
        return rfrank(u*2,k-s[u*2+1].w);
    }
}seg[2];
int n,ax,ay,cnt,p[200'005];
pair<int,int> a[100'005];
bool fail(int x){
    seg[0].build(1,1,cnt),seg[1].build(1,1,cnt);
    for(int i=1;i<=n;i++) seg[1].update(1,a[i].second,1);
    int pos=n;
    for(int i=cnt;i>=1;i--){
        while(pos>=1&&a[pos].first>=i){
            seg[0].update(1,a[pos].second,1);
            seg[1].update(1,a[pos].second,-1);
            --pos;
        }
        if(min(seg[0].s[1].w,seg[1].s[1].w)<x) continue;
        for(int j=0;j<=1;j++){
            int r=seg[j].rfrank(1,x);
            if(seg[j].s[1].w-seg[j].query(1,r,cnt)<x) continue;
            int res=seg[j^1].query(1,r,cnt);
            if(min(res,seg[j^1].s[1].w-res)<x) continue;
            ax=p[i],ay=p[r];
            return 0;
        }
    }
    return 1;
}
void solution(){
    cin>>n,ax=0,ay=0,cnt=0;
    for(int i=1;i<=n;i++){
        auto &[x,y]=a[i];
        cin>>x>>y;
        p[++cnt]=x,p[++cnt]=y;
    }
    sort(p+1,p+cnt+1);
    cnt=unique(p+1,p+cnt+1)-p-1;
    for(int i=1;i<=n;i++){
        auto &[x,y]=a[i];
        x=lower_bound(p+1,p+cnt+1,x)-p;
        y=lower_bound(p+1,p+cnt+1,y)-p;
    }
    sort(a+1,a+n+1);
    int l=0,r=n/4,ans=0;
    while(l<=r){
        int mid=(l+r)>>1;
        if(fail(mid)) r=mid-1;
        else ans=mid,l=mid+1;
    }
    fail(ans);
    cout<<ans<<'\n'<<ax<<' '<<ay<<'\n';
}
int T;
int main(){
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    cin>>T;
    while(T--) solution();
    return 0;
}
最后修改:2024 年 12 月 30 日
如果觉得我的文章对你有用,请随意赞赏