线段树应用练习
扫描线
P5490【模板】扫描线 & 矩形面积并
(并非板子题
有人说它是板子题,但我认为它比二维数点复杂多了
首先考虑最暴力的暴力,我们暴力用矩形覆盖每一个点,然后再统计哪些点被覆盖了
然后我们采用二维数点的思想,对
考虑
其次怎么处理
接下来问题简化成了:
我们有一个序列,最开始次数都为0,每个点有权值和一个次数,(权值就是这个节点代表的段的长度是不会变的,所有操作都针对次数),然后我们会有
这里要是朴素的线段树复杂度会炸,这里用到了一种不下传标记
对于一次修改操作,我们先将这个大区间拆分成 log 个小区间,对于小区间的次数进行 +1 / -1,如果小区间的次数大于 0,那么我们就知道这个区间所有点都选择了,那么权值和即为这个区间所有点权值之和;反之,我们就将权值和设为其左右两个子区间权值和之和,因为在修改时我们只修改有关区间,所以对于子区间和父区间它们的次数都是独立的,最后注意特判区间无法拆分也就是叶子节点的情况
为什么可以使用不下传标记呢,两点:
- 每一个 2 操作在前面都能找到 1 操作与其对应,也就是当我们进行区间减时,我们和之前加操作拆分出来的区间是相同的,拆分出来的每个区间都有之前加的标记,而每一个加的标记又恰好能在这次删除操作被删除
- 我们只查询最大的区间,考虑如果要查询一个小区间,那么我们在它上面大区间的标记就不会影响到下边,答案就不正确
代码:
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+5;
int n,cntx,cnty,lenx,leny,ans;
int x1[N],x2[N],Y1[N],y2[N],ax[N],ay[N],numx[N],numy[N];
map<int,int>mpx,mpy;
struct edge{
int l,r,op;
};
vector<edge>e[N];
struct tree{
int num,sum,cnt;
}tr[N];
void build(int k,int l,int r){
if(l==r){
tr[k].num=numy[l];
return;
}
int mid=(l+r)>>1;
build(k*2,l,mid);
build(k*2+1,mid+1,r);
tr[k].num=tr[k*2].num+tr[k*2+1].num;
}
void change(int k,int l,int r,int z){
tr[k].cnt+=z;
if(tr[k].cnt) tr[k].sum=tr[k].num;
else if(l==r) tr[k].sum=0;
else tr[k].sum=tr[k*2].sum+tr[k*2+1].sum;
}
void longchange(int k,int l,int r,int x,int y,int z){
if(x<=l&&r<=y){
change(k,l,r,z);
return;
}
int mid=(l+r)>>1;
if(x<=mid) longchange(k*2,l,mid,x,y,z);
if(y>mid) longchange(k*2+1,mid+1,r,x,y,z);
change(k,l,r,0);
}
signed main(){
scanf("%lld",&n);
for(int i=1;i<=n;i++){
scanf("%lld%lld%lld%lld",&x1[i],&Y1[i],&x2[i],&y2[i]);
ax[++cntx]=x1[i],ax[++cntx]=x2[i];
ay[++cnty]=Y1[i],ay[++cnty]=y2[i];
}
sort(ax+1,ax+1+cntx);
sort(ay+1,ay+1+cnty);
lenx=unique(ax+1,ax+1+cntx)-ax-1;
leny=unique(ay+1,ay+1+cnty)-ay-1;
for(int i=1;i<=lenx;i++){
mpx[ax[i]]=i;
if(i>1) numx[i]=ax[i]-ax[i-1];
}
for(int i=1;i<=n;i++){
x1[i]=mpx[x1[i]];
x2[i]=mpx[x2[i]];
e[x1[i]].push_back({Y1[i],y2[i],1});
e[x2[i]].push_back({Y1[i],y2[i],0});
}
for(int i=1;i<=leny;i++){
mpy[ay[i]]=i;
numy[i]=ay[i+1]-ay[i];
}
for(int i=1;i<=lenx;i++){
for(int j=0;j<e[i].size();j++){
int l=e[i][j].l,r=e[i][j].r,op=e[i][j].op;
e[i][j]={mpy[l],mpy[r]-1,op};
// printf("%lld %lld %lld\n",e[i][j].l,e[i][j].r,e[i][j].op);
}
}
leny--;
build(1,1,leny);
for(int i=1;i<=lenx;i++){
ans+=numx[i]*tr[1].sum;
for(auto j:e[i]){
if(j.op==1) longchange(1,1,leny,j.l,j.r,1);
else longchange(1,1,leny,j.l,j.r,-1);
}
}
printf("%lld",ans);
}
P1856 [IOI 1998 ] [USACO5.5] 矩形周长Picture
可以把其转化为矩形面积并相同的做法
首先可以将答案拆分成:从左到右垂直于
然后其余的都没有变,我们每加入一条边,就求一下序列总和,如果总和相比于上次增加了,就相当于是加上了一条入边,就加上增加了多少的贡献,反之则不加
注:考虑当两个矩形入边和出边重合的情况,所以假如我们是枚举 x,线段树维护 y,那么当 x 相同时,我们先处理加边,反之也是一样
代码:
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=2e4+5;
int n,cntx,cnty,m,ans;
int X1[N],X2[N],Y1[N],Y2[N],ax[N],ay[N],numx[N],numy[N];
map<int,int>mpx,mpy;
struct edge{
int l,r,op;
};
vector<edge>e[N];
struct tree{
int num,sum,cnt;
}tr[N];
bool cmp(edge x,edge y){
return x.op>y.op;
}
void build(int k,int l,int r,int z){
tr[k]={0,0,0};
if(l==r){
if(z==1) tr[k].num=numy[l];
if(z==2) tr[k].num=numx[l];
return;
}
int mid=(l+r)>>1;
build(k*2,l,mid,z);
build(k*2+1,mid+1,r,z);
tr[k].num=tr[k*2].num+tr[k*2+1].num;
}
void change(int k,int l,int r,int z){
tr[k].cnt+=z;
if(tr[k].cnt) tr[k].sum=tr[k].num;
else if(l==r) tr[k].sum=0;
else tr[k].sum=tr[k*2].sum+tr[k*2+1].sum;
}
void longchange(int k,int l,int r,int x,int y,int z){
if(x<=l&&r<=y){
change(k,l,r,z);
return;
}
int mid=(l+r)>>1;
if(x<=mid) longchange(k*2,l,mid,x,y,z);
if(y>mid) longchange(k*2+1,mid+1,r,x,y,z);
change(k,l,r,0);
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d%d%d%d",&X1[i],&Y1[i],&X2[i],&Y2[i]);
ax[++cntx]=X1[i];ax[++cntx]=X2[i];
ay[++cnty]=Y1[i];ay[++cnty]=Y2[i];
}
sort(ax+1,ax+1+cntx);
sort(ay+1,ay+1+cnty);
int lenx=unique(ax+1,ax+1+cntx)-ax-1;
int leny=unique(ay+1,ay+1+cnty)-ay-1;
for(int i=1;i<=lenx;i++){
mpx[ax[i]]=i;
numx[i]=ax[i+1]-ax[i];
}
for(int i=1;i<=leny;i++){
mpy[ay[i]]=i;
numy[i]=ay[i+1]-ay[i];
}
for(int i=1;i<=n;i++){
e[mpx[X1[i]]].push_back({mpy[Y1[i]],mpy[Y2[i]]-1,1});
e[mpx[X2[i]]].push_back({mpy[Y1[i]],mpy[Y2[i]]-1,-1});
}
m=leny-1;
build(1,1,m,1);
int lst=0;
for(int i=1;i<=lenx;i++){
sort(e[i].begin(),e[i].end(),cmp);
for(auto j:e[i]){
longchange(1,1,m,j.l,j.r,j.op);
ans+=max(tr[1].sum-lst,0);
lst=tr[1].sum;
}
e[i].clear();
}
for(int i=1;i<=n;i++){
e[mpy[Y1[i]]].push_back({mpx[X1[i]],mpx[X2[i]]-1,1});
e[mpy[Y2[i]]].push_back({mpx[X1[i]],mpx[X2[i]]-1,-1});
}
m=lenx-1;
build(1,1,m,2);
lst=0;
for(int i=1;i<=leny;i++){
sort(e[i].begin(),e[i].end(),cmp);
for(auto j:e[i]){
longchange(1,1,m,j.l,j.r,j.op);
ans+=max(tr[1].sum-lst,0);
lst=tr[1].sum;
}
e[i].clear();
}
printf("%d",ans*2);
}
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!