poj3109坐标离散化+树状数组

61 篇文章 0 订阅

黑白棋・改:无限大的棋盘上,在横向和纵向上被包围的白子会变成黑子,求最终黑子个数?

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

基本思想

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

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

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

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

    (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;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值