线段树扫描线的两个入门题(hdu1255 和 hdu1542)

题一链接:http://acm.hdu.edu.cn/showproblem.php?pid=1255

题目大意:给定平面上若干矩形,求出被这些矩形覆盖过至少两次的区域的面积。

做完这个题之后我才真正理解题二mark数组所代表的真正含义。

当mark[i]为0时,表示这个区间并没有完全被覆盖,但是其区间内的某部分可能被覆盖了。

此时通过其左右子区间进行更新。

当mark[i]为1时,表示这个区间已经完全被覆盖了。

而对于这个题,就变得较为复杂了。但是思想与普通的求矩形围成的面积差不多。

回忆一下一般的求矩形覆盖面积,线段树节点里面有一个重要的变量,cnt。这个变量表示了该节点表示的区间被完全覆盖,如果cnt=0,说明没有被完全覆盖(但不代表没有被覆盖),要算出该节点所代表的区间被覆盖的长度,需要由它左右孩子节点被覆盖的长度相加所得。如果cnt=1,表示被完全覆盖,覆盖长度就是该区间长度。如果cnt>1说明也是被完全覆盖,不过不止覆盖了一次,在算覆盖长度的时候,和cnt=1的计算方法是一样的。注意一点,节点里还有另一个变量len,就是该区间被覆盖的长度,但是我们注意一下,这个len准确的意义应该是,被覆盖了一次或以上的长度,只是这个意义在一般的求面积问题中,不需要过分强调。

而在这题中我们要计算被覆盖两次或以上的部分面积,我们在线段树节点中增设了一个变量,ss,其中s表示该该区间内被覆盖了1次或以上的长度,ss表示被覆盖了2次或以上的长度

我们是怎么计算最后的面积的?一样的道理,从下往上扫描矩形,每次添加一条矩形上下边,然后看看t[1].ss是多少,再乘上高度差。因为t[1]表示了总区间,而ss表示被覆盖两次或以上的长度,即计算时我们忽略掉只被覆盖一次的长度

问题的关键变为怎么计算一个节点的ss

分情况讨论

1.cnt>1 : 说明该区间被覆盖两次或以上,那么长度就可以直接计算,就是该区间的长度

剩下的情况就是cnt=1或cnt=0

2.先看叶子节点,因为是叶子没有孩子了,所以被覆盖两次货以上的长度就是0(无论cnt=1或cnt=0都是0,因为是叶子。。。)

3.不是叶子节点 ,且cnt=1.注意这里,cnt=1确切的意义是什么,应该是,可以确定,这个区间被完全覆盖了1次,而有没有被完全覆盖两次或以上则不知道无法确定,那么怎么怎么办了,只要加上t[lch].s + t[rch].s  即,看看左右孩子区间被覆盖了一次或以上的长度,那么叠加在双亲上就是双亲被覆盖两次或以上的长度

3.不是叶子节点,且cnt=0,确切的意义应该是不完全不知道被覆盖的情况(不知道有没有被覆盖,被覆盖了几次,长度是多少都不知道),这种情况,只能由其左右孩子的信息所得

t[lch].ss + t[rch].ss  , 即直接将左右孩子给覆盖了两次或以上的长度加起来,这样才能做到不重不漏

 

AC代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<map>
#include<set>
#include<stack>
#include<queue>
#include<vector>
using namespace std;
typedef long long ll;
int t,n; 
const int INF=1e3+10;
struct node{
	double from;
	double to;
	int d;
	double h;
}g[INF*2];
double a[INF*2];
struct Node{
	int right;
	int left;
	double sum,sam;
}f[8*INF];
int mark[8*INF];
bool cmp(node p1,node p2)
{
	return p1.h <p2.h ;
}
void build(int ans,int l,int r)
{
	f[ans].sum =0;f[ans].right =r;f[ans].left =l;
	f[ans].sam =0;
	if(l==r)return;
	int mid=(l+r)>>1;
	build(ans<<1,l,mid);
	build(ans<<1|1,mid+1,r); 
}
void pushup(int ans)
{
	if(mark[ans])
	{
		f[ans].sum =a[f[ans].right +1]-a[f[ans].left ];
	}
	else if(f[ans].left ==f[ans].right )
	{
		f[ans].sum =0;
	}
	else {
		f[ans].sum =f[ans<<1].sum +f[ans<<1|1].sum ;
	}
	
	if(mark[ans]>1){
		f[ans].sam =a[f[ans].right +1]-a[f[ans].left ];
	}
	else if(f[ans].left ==f[ans].right ){
		f[ans].sam =0;
	}
	else if(mark[ans]==1)
	{
		f[ans].sam =f[ans<<1].sum +f[ans<<1|1].sum ;
	}
	else {
		f[ans].sam =f[ans<<1].sam +f[ans<<1|1].sam ;
	}
}
void update(int ans,int l,int r,int d)
{
	if(f[ans].left >=l&&f[ans].right <=r)
	{
		mark[ans]+=d;
		pushup(ans);
		return;
	}
	int mid=(f[ans].left +f[ans].right )>>1;
	if(l<=mid)update(ans<<1,l,r,d);
	if(r>mid)update(ans<<1|1,l,r,d); 	
	pushup(ans);
}
int main()
{
	cin>>t;
	while(t--)
	{
	    scanf("%d",&n);
	    if(n==0)continue;
		int i,j;
		for(i=1,j=0;i<=n;i++)
		{
			double x1,y1,x2,y2;
			scanf("%lf%lf%lf%lf",&x1,&y1,&x2,&y2);
		    g[++j].from=x1;g[j].to =x2;
		    g[j].h =y1;g[j].d =1;
		    a[j]=x1;
		    g[++j].from =x1;g[j].to =x2;
		    g[j].h =y2;g[j].d =-1;
		    a[j]=x2;
		}	
		sort(a+1,a+1+j);
		sort(g+1,g+1+j,cmp);
		int cnt=unique(a+1,a+1+j)-a-1;
		memset(mark,0,sizeof(mark));
		build(1,1,cnt-1);
		double ans=0;
		for(i=1;i<j;i++)
		{
			int L=lower_bound(a+1,a+1+cnt,g[i].from )-a;
			int R=lower_bound(a+1,a+1+cnt,g[i].to )-a-1;
			update(1,L,R,g[i].d);
			ans+=f[1].sam *(g[i+1].h -g[i].h );
		}
		printf("%.2lf\n",ans);
	}
    return 0;
}

 

hdu1542 链接:http://acm.hdu.edu.cn/showproblem.php?pid=1542

题目大意:求由这些矩形围成的图形的面积,注意不能重复计算。

解题思路:请看链接:https://blog.csdn.net/xianpingping/article/details/83032798

这里主要写一下我自己觉着难以理解的东西。具体看代码注释。

AC代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<map>
#include<set>
#include<stack>
#include<queue>
#include<vector>
using namespace std;
typedef long long ll;
#define lson i<<1,l,m
#define rson i<<1|1,m+1,r
const int maxn=222;
double x[maxn];
struct node
{
    double l,r,h;//左右坐标,高度
    int d;//标记上位边还是下位边
    node() {}
    node(double l,double r,double h,int d):l(l),r(r),h(h),d(d) {}
    bool operator < (const node &a)const
    {
        return h<a.h;
    }
} line[maxn];
int cnt[maxn<<2];
double sum[maxn<<2];
void pushup(int i,int l,int r)
{
   if(cnt[i])//不为零时,则更新sum[i]的具体值 
   {
       sum[i]=x[r+1]-x[l];
   }
   else//为零时,因为子区间未更新,所以自动更新为零,所以不用pushdown. 
   {
       sum[i]=sum[i<<1]+sum[i<<1|1];
   }
}
void update(int ql,int qr,int v,int i,int l,int r)
{
    if(ql<=l && qr>=r)
    {
       cnt[i]+=v;
       pushup(i,l,r);
       return ;
    }
    int m=(l+r)>>1;
    if(ql<=m)update(ql,qr,v,lson);
    if(qr>m)update(ql,qr,v,rson);
    pushup(i,l,r);
}
int main()
{
    int q;
    int kase=0;
    while(cin>>q&&q)
    {
        memset(cnt,0,sizeof(cnt));//相当于build
        memset(sum,0,sizeof(sum));//相当于build
        int n=0,m=0;
        for(int i=1; i<=q; i++)
        {
            double x1,y1,x2,y2;
            scanf("%lf%lf%lf%lf",&x1,&y1,&x2,&y2);
            x[++n]=x1;
            x[++n]=x2;
            line[++m]=node(x1,x2,y1,1);
            line[++m]=node(x1,x2,y2,-1);
        }
        sort(x+1,x+1+n);
        sort(line+1,line+1+m);
        int k=1;
        k=unique(x+1,x+n+1)-x-1;//直接用STL中的unique函数。
        double ans=0.0;
        for(int i=1; i<m; i++)
        {
            int l=lower_bound(x+1,x+k+1,line[i].l)-x;
            int r=lower_bound(x+1,x+k+1,line[i].r)-x;
            r--;//这样就避免了下面那种错误,自己可以写写分析分析 
            if(l<=r)update(l,r,line[i].d,1,1,k-1);
            ans+=sum[1]*(line[i+1].h-line[i].h);
        }
        printf("Test case #%d\nTotal explored area: %.2f\n\n",++kase,ans);
    }

}
/*这里注意下扫描线段时r-1:int R=search(s[i].l,hash,m)-1;
计算底边长时r+1:if(mark[n])sum[n]=hash[right+1]-hash[left];
解释:假设现在有一个线段左端点是l=0,右端点是r=m-1则我们去更新的时候,
会算到sum[1]=hash[mid]-hash[left]+hash[right]-hash[mid+1]
这样的到的底边长sum是错误的,why?因为少算了mid~mid+1的距离,
由于我们这利用了离散化且区间表示线段,所以mid~mid+1之间是有长度的,
比如hash[3]=1.2,hash[4]=5.6,mid=3所以这里用r-1,r+1就很好理解了 */ 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值