之后有一回在luogu做了一道同样求矩形周长的题,用了下面的模板,但是有组数据过不了,需要做如下修改:
重载运算符函数修改成: bool operator <(const P&p)const{return h==p.h?ju<p.ju:h<p.h;}
之前并不能理解扫描线这种东西,真的以为是条线,还在想这东西到底要怎么实现?
结果理解后,好像还真是条线。
好吧。其实是以一个坐标轴的点确定的直线+长度,直线和长度便确定了线段,也有了值。
扫描线应该说是靠顺序查询轴线点来实现的。
看了一个大佬的博客,受益匪浅。我是看了这个博客才明白扫描线这东西的做法。
总的来说,是先确定一个轴线为遍历方向,以该方向从小到大排序点,
然后扫描线就是:以确定的轴线方向(设为第一坐标轴)为扫描方向,按从小到大的顺序(通常都是从小到大),逐个遍历目标点的坐标,以该点的另一个坐标轴的方向(设为第二坐标轴)确定另一条直线,然后在遍历的过程中,以某种方法(比如线段树),尝试确定当前点的第二坐标轴方向直线的有效长度,
有效长度指的是:当前直线到下一条直线所围面积的底边(即这根直线上的边)的长度。
然后以这个当前有效长度,来实现我们的目的。好比如,计算面积,就是逐个遍历,用当前有效长度,乘以与下一条直线间的距离(即第一坐标轴相邻两点的距离)。
我看的博客的代码,当前有效长度是以sum[1]数组记录。用线段树更新。
另外,有将第一坐标轴的点离散化。
然后根据那个博客,我改了里边的代码,做了我扫描线的第一题,洛谷P1904:
给定以x轴为下底边的矩形的信息,求矩形轮廓外轮廓。
#include<bits/stdc++.h> using namespace std; const int maxn=5e3+50; int mark[maxn<<2];//记录某个区间的下底边个数 int sum[maxn<<2];//记录某个区间的下底边总长度 int a[maxn<<1];//对h进行离散化 //以纵坐标作为线段(区间),对纵坐标线段进行扫描 struct P{ int x,h,ju;//以h为扫描线平行边,x为扫描方向,ju=-1为左 ju=1为右 bool operator <(const P&p)const{return x<p.x;} P(){} P(int xx,int hh,int juu){x=xx;h=hh;ju=juu;} }s[maxn<<1]; void upfather(int k,int l,int r){ if(mark[k])sum[k]=a[r+1]-a[l];// else if(l==r)sum[k]=0; else sum[k]=sum[k<<1]+sum[k<<1|1]; }//n k void update(int L,int R,int ju,int k,int l,int r){ if(L<=l&&r<=R){ mark[k]+=ju; upfather(k,l,r); return; } int mid=(l+r)>>1; if(L<=mid)update(L,R,ju,k<<1,l,mid); if(R>mid)update(L,R,ju,k<<1|1,mid+1,r); upfather(k,l,r); } int search(int key,int *x,int k){ int l=0,r=k-1; while(l<=r){ int mid=(l+r)>>1; if(x[mid]==key)return mid; if(x[mid]>key)r=mid-1; else l=mid+1; } return -1; } struct PP{ int x,y; PP(){} PP(int xx,int yy){x=xx;y=yy;} }ans[maxn<<2]; int solve(int tot,int up)//tot是边数 up是点数 { int upp=0,tans=0; for(int i=0;i<tot;i++){ int R=search(s[i].h,a,up)-1; update(0,R,s[i].ju,1,0,up-1); if(sum[1]==tans)continue; if(ans[upp-1].x==s[i].x&&ans[upp-1].y==tans)upp--; else ans[upp++]=PP(s[i].x,tans); if(ans[upp-1].x==s[i].x&&ans[upp-1].y==sum[1])upp--; else ans[upp++]=PP(s[i].x,sum[1]); tans=sum[1]; } return upp; } int main() { int n,tot=0,l,h,r,up=0; a[up++]=0; // scanf("%d",&n); while(~scanf("%d%d%d",&l,&h,&r)) // while(tot<n*2) { // scanf("%d%d%d",&l,&h,&r); a[up++]=h; s[tot++]=P(l,h,-1); s[tot++]=P(r,h,1); } sort(a,a+up); sort(s,s+tot); up=unique(a,a+up)-a; int upp=solve(tot,up); for(int i=0;i<upp;i++) { if(i&1)printf("%d ",ans[i].y); else printf("%d ",ans[i].x); // printf("%d% d ",ans[i].x,ans[i].y); } }
2019-09-04
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
扫描线第二题 HDU1542
给定矩形信息,求总面积。
#include<bits/stdc++.h> using namespace std; const int maxn=1e2+50; int mark[maxn<<2];//记录某个区间的下底边个数 double sum[maxn<<2];//记录某个区间的下底边总长度 double a[maxn<<1];//对x进行离散化 // struct P{ double l,r,h; int ju; bool operator <(const P&p)const{return h<p.h;} P(){} P(double ll,double rr,double hh,int juu) {l=ll;r=rr;h=hh;ju=juu;} }s[maxn<<1]; void upfather(int k,int l,int r){ if(mark[k])sum[k]=a[r+1]-a[l]; else if(l==r)sum[k]=0; else sum[k]=sum[k<<1]+sum[k<<1|1]; } void update(int L,int R,int ju,int k,int l,int r){ if(L<=l&&r<=R){ mark[k]+=ju; upfather(k,l,r); return; } int mid=(l+r)>>1; if(L<=mid)update(L,R,ju,k<<1,l,mid); if(R>mid)update(L,R,ju,k<<1|1,mid+1,r); upfather(k,l,r); } int search(double key,double *x,int k){ int l=0,r=k-1; while(l<=r){ int mid=(l+r)>>1; if(x[mid]==key)return mid; if(x[mid]>key)r=mid-1; else l=mid+1; } return -1; } double solve(int tot,int up)//tot是边数 up是点数 { double ans=0; for(int i=0;i<tot;i++){ int L=search(s[i].l,a,up); int R=search(s[i].r,a,up)-1; update(L,R,s[i].ju,1,0,up-1); ans+=sum[1]*(s[i+1].h-s[i].h); } return ans; } int main() { int n,cas=0; while(scanf("%d",&n)!=EOF&&n) { cas++; memset(a,0,sizeof(a)); memset(s,0,sizeof(s)); int i,tot=0; double x1,y1,x2,y2; for(i=0;i<n;i++) { scanf("%lf%lf%lf%lf",&x1,&y1,&x2,&y2); a[tot]=x1; s[tot++]=P(x1,x2,y1,-1); a[tot]=x2; s[tot++]=P(x1,x2,y2,1); } sort(a,a+tot); sort(s,s+tot); int up=unique(a,a+tot)-a; printf("Test case #%d ",cas); printf("Total explored area: %.2lf ",solve(tot,up)); } }
2019-09-05
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
第三题,POJ1177
给定矩形信息,求周长。
(好的做法应该是用线段树的连通性维护当前扫描线有几段,然后另一方向的长度直接等于2*num*h,然而我试了,不会做,只能老老实实再在另一方向扫描一遍。)
#include<bits/stdc++.h> #define debug printf("!"); using namespace std; typedef long long ll; const int maxn=1e4+50; const int inf=0x3f3f3f3f; int mark[maxn<<2];//记录某个区间的下底边个数 ll sum[maxn<<2];//记录某个区间的下底边总长度 int a1[maxn<<1],a2[maxn<<1];//对x进行离散化 // struct P{ int l,r,h; int ju; bool operator <(const P&p)const{return h==p.h?ju<p.ju:h<p.h;}// /* 排序需要ju作为第二关键字 测试样例 2 0 0 4 4 0 4 4 8 使得上面图形的底边 和 下面图形的顶边 在重边时避免多算 */ }s1[maxn<<1],s2[maxn<<1]; void upfather(int k,int l,int r,int a[]){ if(mark[k])sum[k]=a[r+1]-a[l]; else if(l==r)sum[k]=0; else sum[k]=sum[k<<1]+sum[k<<1|1]; } void update(int L,int R,int ju,int k,int l,int r,int a[]){ if(L<=l&&r<=R){ mark[k]+=ju; upfather(k,l,r,a); return; } int mid=(l+r)>>1; if(L<=mid)update(L,R,ju,k<<1,l,mid,a); if(R>mid)update(L,R,ju,k<<1|1,mid+1,r,a); upfather(k,l,r,a); } int search(int key,int *x,int k){ int l=0,r=k-1; while(l<=r){ int mid=(l+r)>>1; if(x[mid]==key)return mid; if(x[mid]>key)r=mid-1; else l=mid+1; } return -1; } ll solve(int tot,int up,P s[],int a[])//tot是边数 up是点数 { ll ans=0,tans=0; for(int i=0;i<tot;i++){ int L=search(s[i].l,a,up); int R=search(s[i].r,a,up)-1; update(L,R,s[i].ju,1,0,up-1,a); if(sum[1]-tans>=0)ans+=sum[1]-tans; else ans-=sum[1]-tans; tans=sum[1]; } return ans; } int main() { int i,n,tot=0,up; int x1,y1,x2,y2; ll ans=0; scanf("%d",&n); for(i=0;i<n;i++) { scanf("%d%d%d%d",&x1,&y1,&x2,&y2); a1[tot]=x1; a2[tot]=y1; s1[tot]=P{x1,x2,y1,-1}; s2[tot++]=P{y1,y2,x1,-1}; a1[tot]=x2; a2[tot]=y2; s1[tot]=P{x1,x2,y2,1}; s2[tot++]=P{y1,y2,x2,1}; } sort(a1,a1+tot);sort(a2,a2+tot); sort(s1,s1+tot);sort(s2,s2+tot); up=unique(a1,a1+tot)-a1; ans+=solve(tot,up,s1,a1); memset(sum,0,sizeof(sum)); memset(mark,0,sizeof(mark)); up=unique(a2,a2+tot)-a2; ans+=solve(tot,up,s2,a2); printf("%lld ",ans); }
2019-09-05 20:08:06
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
第四题,洛谷P382
跟第一题几乎一样,改点输入输出和一个小细节就可以了:D。
#include<bits/stdc++.h> using namespace std; const int maxn=1e5+50; int mark[maxn<<2];//记录某个区间的下底边个数 int sum[maxn<<2];//记录某个区间的下底边总长度 int a[maxn<<1];//对h进行离散化 //以纵坐标作为线段(区间),对纵坐标线段进行扫描 struct P{ int x,h,ju;//以h为扫描线平行边,x为扫描方向,ju=-1为左 ju=1为右 bool operator <(const P&p)const{return x<p.x;} P(){} P(int xx,int hh,int juu){x=xx;h=hh;ju=juu;} }s[maxn<<1]; void upfather(int k,int l,int r){ if(mark[k])sum[k]=a[r+1]-a[l];// else if(l==r)sum[k]=0; else sum[k]=sum[k<<1]+sum[k<<1|1]; }//n k void update(int L,int R,int ju,int k,int l,int r){ if(L<=l&&r<=R){ mark[k]+=ju; upfather(k,l,r); return; } int mid=(l+r)>>1; if(L<=mid)update(L,R,ju,k<<1,l,mid); if(R>mid)update(L,R,ju,k<<1|1,mid+1,r); upfather(k,l,r); } int search(int key,int *x,int k){ int l=0,r=k-1; while(l<=r){ int mid=(l+r)>>1; if(x[mid]==key)return mid; if(x[mid]>key)r=mid-1; else l=mid+1; } return -1; } struct PP{ int x,y; PP(){} PP(int xx,int yy){x=xx;y=yy;} }ans[maxn<<2]; int solve(int tot,int up)//tot是边数 up是点数 { int upp=0,tans=0; for(int i=0;i<tot;i++){ int R=search(s[i].h,a,up)-1; update(0,R,s[i].ju,1,0,up-1); if(sum[1]==tans)continue; if(ans[upp-1].x==s[i].x&&ans[upp-1].y==tans&&i!=0)upp--; else ans[upp++]=PP(s[i].x,tans); if(ans[upp-1].x==s[i].x&&ans[upp-1].y==sum[1]&&i!=0)upp--; else ans[upp++]=PP(s[i].x,sum[1]); tans=sum[1]; } return upp; } int main() { int n,tot=0,l,h,r,up=0; a[up++]=0; scanf("%d",&n); // while(~scanf("%d%d%d",&l,&h,&r)) while(tot<n*2) { scanf("%d%d%d",&h,&l,&r); a[up++]=h; s[tot++]=P(l,h,-1); s[tot++]=P(r,h,1); } sort(a,a+up); sort(s,s+tot); up=unique(a,a+up)-a; int upp=solve(tot,up); printf("%d ",upp); for(int i=0;i<upp;i++) { // if(i&1)printf("%d ",ans[i].y); // else printf("%d ",ans[i].x); printf("%d% d ",ans[i].x,ans[i].y); } }
2019-09-06 15:02:29
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////