模拟赛 矩形 (扫描线,三维偏序,线段树合并,并查集,线段树上二分)
作者:互联网
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();
}
标签:偏序,rt,return,int,线段,扫描线,矩形,竖线 来源: https://www.cnblogs.com/lnxxqz/p/16634514.html