您的位置:首页 > 其它

[USACO5.5.1] Picture

2017-08-22 07:38 281 查看
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++)
{
//把这条线段加入线段树
4000

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
*/
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  线段树 扫描线