Atlantis HDU - 1542

原题链接
考察:线段树(扫描线)
思路:
  其实是扫描线入门题.(然后就看了很久才略懂)
  本题思路还是直接看大佬博客吧GO
  想解释的只有几个问题.

  • 为什么结构体如此定义?
struct Node{
	int l,r,cnt;//实际代表含义是[l,r+1]区间
	double len;//cnt是指该区间被覆盖次数 len是该区间有效长度.
}tr[N<<3];

  为什么不用普通线段树一样,l,r表示区间端点呢?这里就涉及了modify函数

void modify(int u,int l,int r,int w)//修改[l,r]区间 
{
	if(tr[u].l>=l&&tr[u].r<=r) 
	{
		tr[u].cnt+=w;
		push_up(u);
		return;
	}
	int mid = tr[u].l+tr[u].r>>1;
	if(mid>=l) modify(u<<1,l,r,w);
	if(mid<r) modify(u<<1|1,l,r,w);
	push_up(u);
}

  可以发现这个线段树是不用懒标记的.push_down是将父节点信息传递给子节点.普通线段树在两处使用push_down:

  1. query函数里,本题每次都是询问整个区间,所以没必要push_down
  2. modify函数里,当当前结点u的区间不能被[l,r]完全覆盖时.
      我们之所以需要push_down,是因为懒标记的区间每次询问或者修改可能不同,所以需要将懒标记分裂开.这里每次询问的区间是对称的,分类讨论的话就是:
  • 当前区间cnt > 0,我们需要让部分区间--,cnt = 0.但此操作前这个部分区间覆盖次数>1,所以即便--后还是要计算整个区间.
  • 当前区间cnt = 0,此时有操作和没有一样.
      在modify函数里,假设,总长度是[1,4]要更新的区间是[1,3].根据线段树会裂开为[1,2]和[3,3].因为[3,3]的len==0,此时只能计算到[1,2]的有效长度,[2,3]就被忽略了.
    &emso; 那么怎么计算有效长度呢?定义yL代表yL~yL+1的区间.那么在修改时,只需要修改[l,r-1]区间.在push_up时,计算len需要L[r+1]-L[l].这样可以有效解决[1,3]未记录的问题.
  • 为什么push_up这么写
void push_up(int u)
{
	if(tr[u].cnt) tr[u].len = rget(tr[u].r+1)-rget(tr[u].l);
	else if(tr[u].l!=tr[u].r) tr[u].len = tr[u<<1].len+tr[u<<1|1].len;
	else tr[u].len = 0;
}

  可以发现只要叶子结点cnt有值就会被更新.如果没有特判防止数组超界.当cnt = 0且为叶子节点时一定要重新赋值0.

Code:

#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
const int N = 10010;
struct Node{
	int l,r,cnt;
	double len;
}tr[N<<3];
struct Segment{
	double x,y1,y2;
	int w;
	bool operator<(const Segment& s){
		return this->x<s.x;
	}
}seg[N<<1];
int n,kcase;
vector<double> alls;
int get(double x)
{
	return lower_bound(alls.begin(),alls.end(),x)-alls.begin()+1;
}
double rget(int x)
{
	return alls[x-1];
}
void build(int u,int l,int r)
{
	tr[u].l = l,tr[u].r = r;
	tr[u].len = 0,tr[u].cnt = 0;
	if(l==r) return;
	int mid = l+r>>1;
	build(u<<1,l,mid); build(u<<1|1,mid+1,r);
}
void push_up(int u)
{
	if(tr[u].cnt) tr[u].len = rget(tr[u].r+1)-rget(tr[u].l);
	else if(tr[u].l!=tr[u].r) tr[u].len = tr[u<<1].len+tr[u<<1|1].len;
	else tr[u].len = 0;
}
void modify(int u,int l,int r,int w)//修改[l,r]区间 
{
	if(tr[u].l>=l&&tr[u].r<=r) 
	{
		tr[u].cnt+=w;
		push_up(u);//if cnt 变成 0,说明此区间的子区间len = 0,最终为0 
		return;
	}
	int mid = tr[u].l+tr[u].r>>1;
	if(mid>=l) modify(u<<1,l,r,w);
	if(mid<r) modify(u<<1|1,l,r,w);
	push_up(u);
}
int main()
{
	while(scanf("%d",&n)!=EOF&&n)
	{
	    alls.clear();
		for(int i=1,j=0;i<=n;i++)
		{
			double x1,y1,x2,y2;
			scanf("%lf%lf%lf%lf",&x1,&y1,&x2,&y2);
			seg[++j] = {x1,y1,y2,1};
			seg[++j] = {x2,y1,y2,-1};
			alls.push_back(y1),alls.push_back(y2);
		}
		seg[0].x = seg[1].x; seg[0].w = 0;
		sort(alls.begin(),alls.end());
		alls.erase(unique(alls.begin(),alls.end()),alls.end());
		sort(seg+1,seg+2*n+1);//2*n个线段 
		double res = 0;
		build(1,1,2*n);
		for(int i=1;i<=2*n;i++)
		{
			double d = seg[i].x-seg[i-1].x;
			res+=tr[1].len*d;
			modify(1,get(seg[i].y1),get(seg[i].y2)-1,seg[i].w);
		}
		printf("Test case #%d\n",++kcase);
		printf("Total explored area: %.2lf\n\n",res);
	}
	return 0;
}
posted @ 2021-05-19 02:50  acmloser  阅读(33)  评论(0编辑  收藏  举报