Java教程

模拟赛 矩形 (扫描线,三维偏序,线段树合并,并查集,线段树上二分)

本文主要是介绍模拟赛 矩形 (扫描线,三维偏序,线段树合并,并查集,线段树上二分),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

PRO

题目大意:给定$N$个矩形,求连通块个数。($1 \leq N,x_1,x_2,y_1,y_2 \leq 100000$)

SOL

乍一看就能知道是扫描线,不过这题的细节恐怖的要命。

(std同样看不懂,自己魔改了一下)

首先把完全相同的矩形去掉。

之后咱们可以发现,被其他矩形完全包含的小矩形对答案没有任何贡献,所以可以去除。

这一步可以用三维偏序来实现,即对于每一个$i$,如果$\exists j , j_{x1} \leq i_{x1},j_{x2} \geq i_{x2},j_{y1} \leq i_{y1},j_{y2} \geq i_{y2}$,则说明矩形$i$被完全包含。

把前三位cdq,求最后一维的最大值就行。

之后我们会发现,对于两个矩形来说,他们相交当且仅当他们有至少一对边相交,于是这个问题就变成了给你一堆线段,求线段的连通块个数,因为矩形的四条边一定联通。

我们考虑,可以将当前的竖线插入线段树,同时线段树维护区间竖线个数(一会有用),以及每个下标竖线的id(因为如果有x坐标相同的,一定相交联通,属于一个并查集,所以只记录一个即可)。

之后考虑横线,对于一条横线$[l,r]$来说,他与当前$[l,r]$内所有竖线相交,可以利用线段树查询,但是这就出现了一个问题,查询会查到和这条横线在同一并查集的竖线,这就麻烦了。我们就开 线段个数 个线段树,以及一棵总线段树,在修改时同时修改并查集根所在线段树以及总线段树,然后查询时直接用总线段树的个数减当前并查集根线段树的个数。

在线段树上二分,查找最左面的不在本并查集的竖线。每次找到,就将其并查集与本并查集合并,同时将两棵线段树合并。

细节特别多。

大概是std速度的1.5~2倍。

using namespace std;
#include<bits/stdc++.h>
namespace zhu{
#define MY_FILE "d"
void file(){
    freopen(MY_FILE ".in","r",stdin);
    freopen(MY_FILE ".out","w",stdout);
}
const int maxn=2e5+5;
#define MAXN 100000
void ckm(int &x,const int y){
    if(y<x) x=y;
}
int N;
struct mt{
    int x1,x2,y1,y2;
    bool ig;
    friend bool operator == (const mt &a,const mt &b){
        return a.x1==b.x1&&a.x2==b.x2&&a.y1==b.y1&&a.y2==b.y2;
    }
    friend bool operator != (const mt &a,const mt &b){
        return !(a==b);
    }
}m[maxn];
namespace __cdq{
bool cmp2(const mt &a,const mt &b){
    if(a.x2==b.x2) return a.y1<b.y1;
    else return a.x2<b.x2;
}
int tr[maxn];
#define lb(x) (x&-x)
void c(int x,int w){
    for(;x<=MAXN;x+=lb(x)) ckm(tr[x],w);
}
void dl(int x){
    for(;x<=MAXN;x+=lb(x)) tr[x]=0x7fffffff;
}
int q(int x){
    int p=0x7fffffff;
    for(;x;x-=lb(x)) ckm(p,tr[x]);
    return p;
}
#define M ((l+r)>>1)
void cdq(int l,int r){
    if(l==r) return;
    cdq(l,M),cdq(M+1,r);
    sort(m+l,m+M+1,cmp2);
    sort(m+M+1,m+r+1,cmp2);
    for(int i=l,j=M+1;j<=r;j++){
        while(i<=M&&m[i].x2<=m[j].x2) c(m[i].y1,m[i].y2),i++;
        m[j].ig|=(q(m[j].y1)<=m[j].y2);
    }
    for(int i=l;i<=M;i++) dl(m[i].y1);
}
void Slove(){
    for(int i=0;i<=MAXN;i++) tr[i]=0x7fffffff;
    for(int i=1;i<=N;i++) m[i].x2=MAXN+1-m[i].x2,m[i].y2=MAXN+1-m[i].y2; // x1<x1 x2<x2 y1<y1 qu y2 max
    auto cmp1=[&](const mt &a,const mt &b){
        if(a.x1==b.x1){
            if(a.x2==b.x2) return a.y1<b.y1;
            else return a.x2<b.x2;
        }else return a.x1<b.x1;
    };
    sort(m+1,m+N+1,cmp1);
    int j=0;
    for(int i=1;i<=N;i++){
        if(i==1||m[i]!=m[i-1])m[++j]=m[i];
    }
    N=j;
    cdq(1,N);
    for(int i=1;i<=N;i++) m[i].x2=MAXN+1-m[i].x2,m[i].y2=MAXN+1-m[i].y2;
    j=0;
    for(int i=1;i<=N;i++){
        if(!m[i].ig)m[++j]=m[i];
    }
    N=j;
}
}
struct hx{
    int l,r;
    int id;
};
vector<hx>h[maxn];
struct sx{
    int p,id;
};
vector<sx>adds[maxn],dels[maxn];
int f[maxn<<2];
int find(int x){return f[x]==x?x:f[x]=find(f[x]);}
int root[maxn<<2],tmp=0;
const int maxt=maxn*200;
int ls[maxt],rs[maxt],tr[maxt];
void pushup(int rt){
    tr[rt]=tr[ls[rt]]+tr[rs[rt]];
}
int merge(int x,int y,int l,int r){
    if(!x||!y) return x|y;
    if(!tr[x]) return y;
    if(!tr[y]) return x;
    if(l==r) return tr[x]+=tr[y],x;
    ls[x]=merge(ls[x],ls[y],l,M);
    rs[x]=merge(rs[x],rs[y],M+1,r);
    pushup(x);
    return x;
}
int num[maxn];
void ch(int &rt,int l,int r,int c,int w,int t){
    if(!rt) rt=++tmp;
    if(l==r){
        if(w>0) num[l]=t;
        tr[rt]+=w;
        if(tr[rt]==0) num[l]=0;
        return;
    }
    if(c<=M) ch(ls[rt],l,M,c,w,t);
    else ch(rs[rt],M+1,r,c,w,t);
    pushup(rt);
}
int find(int rt,int rtk,int l,int r,int L,int R){
    if(!rt) return -1;
    if(R<l||r<L) return -1;
    if(l==r) return tr[rt]==tr[rtk]?-1:l;
    if(L<=l&&r<=R){
        if(tr[ls[rt]]-tr[ls[rtk]]>0) return find(ls[rt],ls[rtk],l,M,L,R);
        else return find(rs[rt],rs[rtk],M+1,r,L,R);
    }
    int x=find(ls[rt],ls[rtk],l,M,L,R);
    if(~x) return x;
    else return find(rs[rt],rs[rtk],M+1,r,L,R);
}
int MAIN(){
    file();
    cin>>N;
    for(int i=1;i<=N;i++){
        int a,b,c,d;
        scanf("%d%d%d%d",&a,&b,&c,&d);
        // h[b].push_back((hx){a,c});
        // h[d].push_back((hx){a,c});
        m[i]=(mt){a,c,b,d,0};
    } 
    __cdq::Slove();
    for(int i=1;i<=N;i++){
        int x1=m[i].x1,x2=m[i].x2,y1=m[i].y1,y2=m[i].y2;
        h[y1].push_back((hx){x1,x2,i*4-3});
        h[y2].push_back((hx){x1,x2,i*4-2});
        adds[y1].push_back((sx){x1,i*4-1}),adds[y1].push_back((sx){x2,i*4});
        dels[y2+1].push_back((sx){x1,i*4-1}),dels[y2+1].push_back((sx){x2,i*4});
    }
    for(int i=1;i<=N<<2;i++) f[i]=i;
    for(int i=1;i<=MAXN;i++){
        for(auto c:dels[i]){
            ch(root[find(c.id)],1,MAXN,c.p,-1,0);
            ch(root[0],1,MAXN,c.p,-1,0);
        }
        for(auto c:adds[i]){
            if(num[c.p]){
                root[c.id]=root[find(num[c.p])];
                f[find(num[c.p])]=c.id;
            }
            ch(root[c.id],1,MAXN,c.p,1,c.id);
            ch(root[0],1,MAXN,c.p,1,c.id);
        }
        for(auto c:h[i]){
            int a=c.l,b=c.r,id=c.id;
            while(1){
                int x=find(root[0],root[id],1,MAXN,a,b);
                if(x==-1) break;
                root[id]=merge(root[id],root[find(num[x])],1,MAXN);
                f[find(num[x])]=id;
            }
        }
    }
    int ans=0;
    for(int i=1;i<=N<<2;i++) ans+=find(i)==i;
    cout<<ans<<endl;
    return 0;
}
};
#undef int
using namespace zhu;
int main(){
    return MAIN();
}

 

这篇关于模拟赛 矩形 (扫描线,三维偏序,线段树合并,并查集,线段树上二分)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!