您的位置:首页 > 编程语言 > C语言/C++

poj3109坐标离散化+树状数组

2015-03-06 21:09 309 查看
黑白棋・改:无限大的棋盘上,在横向和纵向上被包围的白子会变成黑子,求最终黑子个数?

扫描线算法:扫描线算法的目标是计算黄颜色多边形内部的像素点,最终将其涂色。

基本思想

        按扫描线顺序,计算扫描线与多边形的相交区间,再用要求的颜色显示这些区间的象素,即完成填充工作。

对于一条扫描线填充过程可以分为四个步骤:

    (1)  求交:计算扫描线与多边形各边的交点

    (2)  排序:把所有交点按 x 坐标递增顺序来排序

    (3)  配对:确定扫描线与多边形的相交区间,第一个与第二个,第三个与第四个等等,每对交点代表扫描线与多边形的一个相交区间

    (4)  填充:显示相交区间的象素

统计x的时候,需要将一个区间[交点n的x + 1,交点n+1的x - 1]的个数统统加一,树状数组本身做加法的话是logn,做n次的话是nlogn,太浪费了,BIT可以高效地求一段区间和,以及更新单个元素的值,但是无法高效地更新一个区间的值。

于是可以用双BIT来实现,一个维护初始个数,一个维护累加值,可以将复杂度控制在logn。

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

#define MAX_N 100000+16
typedef long long LL;
LL bit0[MAX_N],bit1[MAX_N];//双Bit实现O(lgn)
static int N,X[MAX_N],Y[MAX_N];
static vector<int>line[MAX_N];//每个黑子构成的一条条水平线
static bool visited[MAX_N];//是否已经算过了

//求和sum(a[t],0<=t<=i
LL sum(LL*b,int i){
int s = 0;
while(i>0){
s+=b[i];
i -=(i&-i);
}
return s;
}

//求和从a[from,to)

LL sum(LL*b,int from,int to){
return sum(b,to-1) - sum(b,from-1);
}

//执行a[i]+=v
void add(LL*b,int i,LL v){
while(i<=MAX_N)
{
b[i] +=v;
i+=(i&-i);
}
}

//基础Bit 结束
//双BIT
//维护的是区间的增量
//[0,n]
//只要在l,r+1处赋值那么就可以求得从[l,r]的增量和
LL sum(int i){
return sum(bit1,i)*i + sum(bit0,i);
}

//(from,to]
LL sum(int from,int to){
return sum(to) - sum(from);
}

//[from,to]
void add(int from,int to,LL x){
add(bit0,from,-x*(from-1));
add(bit1,from,x);
add(bit0,to,x*to);
add(bit1,to,-x);
}

//压缩的方法就是有几种坐标
//x为坐标数组,长度为N
int compress(int *p){

vector<int>ps(N);
for (int i=0;i<N;++i)
{
ps[i] = p[i];
}
sort(ps.begin(),ps.end());
ps.erase(unique(ps.begin(),ps.end()),ps.end());
for (int i=0;i<N;++i)
{
p[i] = 1+distance(ps.begin(),lower_bound(ps.begin(),ps.end(),p[i]));
//算出p[i]在第几个位置而确定是第几种x坐标
}
return ps.size();
}

int main(int argc,char**argv){
scanf("%d",&N);
for(int i=0;i<N;++i)
scanf("%d%d",X+i,Y+i);
int w = compress(X);//压缩X坐标
int h = compress(Y);

//压缩Y坐标后程扫描线
//就是第几种Y放第几种X
for (int i=0;i<N;++i)
{
line[Y[i]].push_back(X[i]);//一条条扫面线
}

LL result = N;
for(int y=1;y<=h;++y){
//对每条扫描线计算上面空白点的个数
vector<int>&xs = line[y];//纵坐标为ydd的那些点横坐标
sort(xs.begin(),xs.end());

for (vector<int>::iterator i = xs.begin();i!=xs.end();++i)
{
int x = *i;
LL s = sum(x-1,x);//横坐标为x的空白点个数(x-1,x]区间的增数

if (visited[x])
result += s;//要染色的像素点
else
visited[x] = true;

add(bit0,x,-s);//add(x,x,-s)复位操作
if (i+1!=xs.end())
{
//到下一个黑点之间的空白点个数
if (x+1<*(i+1)-1)//下一个黑子这样多于1个空白的时候更新的是区间
add(x+1,*(i+1)-1,1);//遇到了空白
else if (x+1==*(i+1)-1)//否则相当于更新单个点
add(bit0,x+1,1);//此时等同于add(x + 1, x + 1, 1);
}
}
}

cout<<result<<endl;
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  algorithm poj c++ 算法 stl