[USACO5.5.1] Picture

Picture
IOI 1998

A number, N (1 <= N < 5000), of rectangular posters, photographs and other pictures of the same shape are pasted on a wall. Their sides are all vertical or horizontal. Each rectangle can be partially or totally covered by the others. The length of the boundary of the union of all rectangles is called the perimeter. Write a program to calculate the perimeter.

Figure 1 shows an example with seven rectangles: 

Figure 1. A set of seven rectangles

The corresponding boundary is the whole set of line segments drawn in Figure 2: 
 
Figure 2. The boundary of the set of rectangles

The vertices of all rectangles have integer coordinates. All coordinates are in the range [-10000,10000] and any existing rectangle has a positive area. The numeric value of the result fits in a 32-bit signed representation.

PROGRAM NAME: picture

INPUT FORMAT

Line 1:N, the number of rectangles pasted on the wall.
Lines 2..N+1In each of the subsequent lines, one can find the integer coordinates of the lower left vertex and the upper right vertex of each rectangle. The values of those coordinates are given as ordered pairs consisting of an x-coordinate followed by a y-coordinate.

SAMPLE INPUT (file picture.in)

7
-15 0 5 10
-5 8 20 25
15 -4 24 14
0 -6 16 4
2 15 10 22
30 10 36 20
34 0 40 16

OUTPUT FORMAT

A single line with a non-negative integer which corresponds to the perimeter for the input rectangles.

SAMPLE OUTPUT (file picture.out)

228


题意:给定一些矩形,求矩形的周长并。

做法:

考虑扫描线

先把某个坐标离散化,线段树维护。——明明可以暴力我偏偏要用线段树去维护。

怎么维护呢?保存区间有多少不连续的段(那么这个数目*2就是横着的边的数量),以及区间有多少被覆盖(这个数目和前面的数目的差的绝对值就是增加的周长的一部分)。

考虑入边和出边重合,则先处理入边,再处理出边,防止多算。

其实这是一道经典题,可以参见szO陈宏大牛Orz的论文。

给一份详细注释的代码——可能有错

#include <algorithm>
#include <iostream>
#include <cstdio>
#include <cstring>
//之所以不是按x值枚举,而是按照扫描线枚举,是因为防止重边,导致我又有加,又有减的,纵向边的长度算得就不是正真的值
//我们假设扫描线是竖的,从左往右扫
const int maxn=20050;
struct L
{
	int x;
	int y1;//表示线的开始 
	int y2;//表示线的结束位置 
	int flag;//1表示入边,-1表示出边 
}line[maxn];
int point[maxn*2],hash[maxn*2+1],cnt;
int now_line_num,now_len,n;
int now;
int ans;
int x1,x2,y1,y2;
struct T
{
	int left,right;//哈希过后的范围
	int true_left,true_right;//正真的范围 
	bool lbd,rbd;//左右端点是否被覆盖,有为true,没有为false 
	int count;//被覆盖了多少次
	int line_num;//内部有多少分开的线段 
	int len;//非0的长度和 
}tree[maxn*4];

void build(int nod,int l,int r) 
{
	tree[nod].left=l;
	tree[nod].right=r;
	tree[nod].true_left=hash[l];
	tree[nod].true_right=hash[r];
	tree[nod].lbd=false;
	tree[nod].rbd=false;
	tree[nod].line_num=0;
	if (r-l>1)
	{
		int mid=(l+r) >> 1;
		build(nod*2,l,mid);
		build(nod*2+1,mid,r);//继续往下递归建树 
	}
}

void update_line_num(int root)//有多少是分开的线段 
{
	if (tree[root].count>0) 
	{
		tree[root].line_num=1;
		tree[root].lbd=true;
		tree[root].rbd=true;
	}
	else 
	if (tree[root].right-tree[root].left==1)
	{
		tree[root].line_num=0;
		tree[root].lbd=false;
		tree[root].rbd=false;
	}
	else
	{
		tree[root].line_num=tree[root*2].line_num+tree[root*2+1].line_num;
		if ((tree[root*2].rbd) && (tree[root*2+1].lbd)) tree[root].line_num--;
		tree[root].lbd=tree[root*2].lbd;
		tree[root].rbd=tree[root*2+1].rbd;
	}
}

void update_len(int root)
{
	if (tree[root].count>0)
	{
		tree[root].len=tree[root].true_right-tree[root].true_left;
	}
	else
	if (tree[root].right-tree[root].left==1)
	{
		tree[root].len=0;
	} 
	else
	{
		tree[root].len=tree[root*2].len+tree[root*2+1].len;
	}
}

void change(int nod,int l,int r,int changeval)
{

	if ((l==tree[nod].left) && (r==tree[nod].right))
	{
		tree[nod].count+=changeval;
		//这个不用改,因为每个这样的段,要么就整个包含在轮廓中,要么整个没有被轮廓包括 
		//同理,不用pushdown,因为没有查询,调用的是tree[1]
	}
	else
	if (tree[nod].right-tree[nod].left==1)
	{
		return;
	} 
	else
	{ 
		int mid=(tree[nod].left+tree[nod].right) >> 1;
		if ((tree[nod].left<=l) && (r<=mid))
		{
			change(nod*2,l,r,changeval);
		}	//在右边部分 
		else
		if ((mid<=l) && (r<=tree[nod].right))
		{
			change(nod*2+1,l,r,changeval);
		}
		else
		{
			change(nod*2,l,mid,changeval);
			change(nod*2+1,mid,r,changeval);
		}
	}
	update_line_num(nod);
	//更新内部有多少分开的线段
	update_len(nod); 
	//更新有多少非0线段 
}


bool cmp1(int x,int y){return x<y;}
void discretization()
{
	std::sort(point,point+cnt,cmp1);//排序,从小到大
	hash[0]=1;//保存有几个离散化的数 
	hash[1]=point[0];
	for (int i=1;i<cnt;i++) 
	if (hash[hash[0]]!=point[i])
	{
		hash[0]++;
		hash[hash[0]]=point[i];
	}
}	//y轴离散化 

int find(int x)//找出x的 
{
	int left=1,right=hash[0];
	while (left<right)
	{
		int mid=(left+right) >> 1;
			 if (hash[mid]<x) left=mid+1;
		else if (hash[mid]>x) right=mid-1;
		else 				  return mid;
	}
	return left;
}

bool cmp2(L a,L b)
{
	if (a.x==b.x) return (a.flag>b.flag);//对于同一个x轴的,入边优先,因为重边不算轮廓 
	return (a.x<b.x);
}

int main() 
{
	scanf("%d",&n);
	cnt=0;
	for (int i=1;i<=n;i++)
	{
		scanf("%d%d%d%d",&x1,&y1,&x2,&y2);//分别表示左下角点的坐标和右上角点的坐标
		line[cnt].x=x1;
		line[cnt].y1=y1;
		line[cnt].y2=y2;
		line[cnt].flag=1;//下边(入边),标记为1 
		point[cnt]=y1;
		cnt++;
		line[cnt].x=x2;
		line[cnt].y1=y1;
		line[cnt].y2=y2;
		line[cnt].flag=-1;//上面那条边(即出边) 
		point[cnt]=y2;
		cnt++;
	}
	discretization();//离散化,排序并去掉重复的值,保存在hash数组中 
	std::sort(line,line+cnt,cmp2);//把扫描线按照x排序 
	build(1,1,hash[0]);//建立线段树 
	now_line_num=0;//保存之前线段树上有值的个数为几个,用于计算纵向轮廓
	now_len=0;//保存有之前线段树上有多少个不连续的线段,用于计算横向轮廓 
	ans=0;
	for (int i=0;i<cnt;i++)
	{
		//把这条线段加入线段树 
		change(1,find(line[i].y1),find(line[i].y2),line[i].flag);
		if (i>0)
		{
			ans+=(line[i].x-line[i-1].x) * 2 * now_line_num;
		}
		printf("%d %d\n",now_len,tree[1].len);
		ans+= abs (now_len - tree[1].len);
		printf("%d\n",ans); 
		now_line_num= tree[1].line_num;
		now_len=tree[1].len;
	}
	printf("%d",ans);
}

再给一份现在刚写的精简的代码

/*
ID:cqz15311
LANG:C++
PROG:picture
*/
#include<bits/stdc++.h>
using namespace std;
const int maxn = 5005;
//---------------------------------------------------
int y[maxn*2],Hash[maxn*2];
int X1[maxn],X2[maxn],Y1[maxn],Y2[maxn];
int n,m;
int find(int x){
	int L = 1,R = Hash[0];
	while (L < R){
		int Mid = (L + R) >> 1;
		if (Hash[Mid] < x)	L = Mid + 1; else
		if (Hash[Mid] > x)	R = Mid - 1; else
						 	return Mid;
	}
	return L;
}
void init(){
	scanf("%d",&n);
	for (int i=1;i<=n;i++){
		scanf("%d%d%d%d",&X1[i],&Y1[i],&X2[i],&Y2[i]);
		y[++y[0]] = Y1[i];
		y[++y[0]] = Y2[i];
	}
	sort(y+1,y+1+y[0]);
	Hash[0] = 0;
 	for (int i=1;i<=y[0];i++){
		if (i == 1 || (y[i]!=y[i-1])){
			Hash[++Hash[0]] = y[i];
		}
	}
}

struct Ques{
	int X,Y1,Y2,type;
	bool operator < (const Ques &t) const{
		return X < t.X || X == t.X && type > t.type;
	}
}Q[maxn*2];

void Add(int X,int Y1,int Y2,int type){
	m++;
	Q[m] . X = X;
	Q[m] . Y1 = Y1;
	Q[m] . Y2 = Y2;
	Q[m] . type = type;
}

void Add_question(){
	m = 0;
	for (int i=1;i<=n;i++){
		Add(X1[i],find(Y1[i]),find(Y2[i]),1);
		Add(X2[i],find(Y1[i]),find(Y2[i]),-1);
	}
	sort(Q+1,Q+1+m);
}

//--------------------------------------------------------------------
struct T{
	int cnt;
	int num,len;
	bool lbd,rbd;
}tree[maxn*4];
void build(int nod,int l,int r){
	tree[nod] . cnt = 0;
	tree[nod] . lbd = tree[nod].rbd = false;
	tree[nod] . num = 0;//有多少个分散的线段 
	tree[nod] . len = 0;//非0的长度 
	if (r-l>1){
		int mid = (l + r) >> 1;
		build(nod*2,l,mid);
		build(nod*2+1,mid,r);
	}
}

void update_len(int nod,int l,int r){
	if (tree[nod].cnt)  tree[nod].len = Hash[r] - Hash[l];else
	if (r-l==1) 		tree[nod].len = 0; else
						tree[nod].len = tree[nod*2].len + tree[nod*2+1].len;
}

void update_num(int nod,int l,int r){
	if (tree[nod].cnt){
		tree[nod].num = 1;
		tree[nod].lbd = tree[nod].rbd = true;
	} else
	if (r-l==1){
		tree[nod].num = 0;
		tree[nod].lbd = tree[nod].rbd = false;
	} else{
		tree[nod].num = tree[nod*2].num + tree[nod*2+1].num - (tree[nod*2].rbd && tree[nod*2+1].lbd);
		tree[nod].lbd = tree[nod*2].lbd;
		tree[nod].rbd = tree[nod*2+1].rbd; 
	}
}

void update(int nod,int l,int r){
	update_num(nod,l,r);
	update_len(nod,l,r);
}


void change(int nod,int l,int r,int x,int y,int val){
	if ((l == x) && (r == y)){
		tree[nod] .cnt+=val;
	} else{
		int mid = (l + r) >> 1;
		if (l <= x && y <= mid)	change(nod*2,l,mid,x,y,val); else
		if (mid <= x && y <= r) change(nod*2+1,mid,r,x,y,val); else{
			change(nod*2,l,mid,x,mid,val);
			change(nod*2+1,mid,r,mid,y,val);
		}
	}
	update(nod,l,r);
}
//------------------------------------------------------------
void solve(){
	build(1,1,Hash[0]);
	int Ans = 0;
	int pre_num = 0,pre_len = 0;
	for (int i=1;i<=m;i++){
		change(1,1,Hash[0],Q[i].Y1,Q[i].Y2,Q[i].type);
		if (i > 1)
			Ans = Ans + (Q[i].X - Q[i-1].X) * 2 * pre_num;
		Ans += abs(pre_len - tree[1].len);
		pre_num = tree[1].num;
		pre_len = tree[1].len;
	}
	printf("%d\n",Ans);
}
//--------------------------------------------------

int main(){
	freopen("picture.in","r",stdin);
	freopen("picture.out","w",stdout);
	init();
	Add_question();
	solve();
	fclose(stdin);
	fclose(stdout); 
	return 0;
}
/*
Executing...
   Test 1: TEST OK [0.000 secs, 4808 KB]
   Test 2: TEST OK [0.000 secs, 4808 KB]
   Test 3: TEST OK [0.000 secs, 4808 KB]
   Test 4: TEST OK [0.000 secs, 4808 KB]
   Test 5: TEST OK [0.000 secs, 4808 KB]
   Test 6: TEST OK [0.000 secs, 4808 KB]
   Test 7: TEST OK [0.000 secs, 4808 KB]
   Test 8: TEST OK [0.000 secs, 4808 KB]
   Test 9: TEST OK [0.000 secs, 4808 KB]
   Test 10: TEST OK [0.000 secs, 4808 KB]
   Test 11: TEST OK [0.000 secs, 4808 KB]

All tests OK.
YOUR PROGRAM ('picture') WORKED FIRST TIME!  That's fantastic
-- and a rare thing.  Please accept these special automated
*/


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值