poj 2528

poj2528 Mayor'sposters


题目链接:http://poj.org/problem?id=2528


题目大意:

在一张墙上张贴海报。每张海报有一个张贴区间。后面放的海报与前面放的海报的关系是:覆盖,不相交,相交。给定海报数目与这些海报分别张贴的区间,问:所有的海报张贴完毕后,可以从墙上看见几张海报。


思路:

乍一看,我们可以使用一个长数组记录海报的信息。比如第一张的区间为[a,b],那么可以将数组的位置[a,b]设为1。若第二张的区间为[c,d],那么将[c,d]设为2...以此类推,在程序的最后,我们只需统计长数组中出现的不同数字的个数即可。算法的复杂度为O(nL+ L) O(nL),由于n的取值可达10,000L的取值可达10,000,000。数组开不了这么大,时间限制又只有1000ms,这是不可能完成的任务。

再一看,采用线段树离散化处理可以很好地解决这个问题。由于坐标的取值可达10,000,000,然而n的取值仅可达10,000。说明分布在区间中的海报是稀疏的。使用离散化可以将坐标的范围极大地缩小,所开数组的长度至多为O(2n),内存限制没有问题!使用线段树对区间进行操作,复杂度为O(nlog2n)O(nlogn),时间限制没有问题!接下来要解决的问题主要是:

区间离散化

更新区间

读取海报标记的问题:

Tips

我的线段树采用一个dat[]数组存储,dat[i]数组存储的是某节点对应的海报序号。

区间离散化:

离散化是个很好的技术,可以把稀疏分布在大范围内的区间压缩到小范围内,且不影响区间之间的覆盖关系。我也是在网上找了许多博客,才了解这个技术的。(上课没来哈...)

基本的思想:

对于n个海报对应的n个区间来说,定义一个结构体line存储2n个端点,该结构体中存储2个值:val(端点值)num(属于第num个海报)。最后对line数组进行升序排序、去重。然后,没错!每一个端点对应的序号就是它们在数组中的顺序。将原来每个区间中的2个端点的值替换成它们对应的序号,这样一来,我们就可以构造出新的n个区间了。而且长数组的范围被压缩到了(0,2n]


更新区间

Tips:dat[i] = 0表示,节点i的区间没贴海报,或不只一张海报。dat[i]> 0表示,节点i的区间张贴了海报dat[i]


void update ( fr , to , L , R , k , po)

使用update函数更新线段树:


            1.当张贴区间[fr,to]与当前节点k的区间[L,R]不相交时,return;

            2.[fr,to]恰好覆盖节点k的区间[L,R]时,标记已贴海报为poreturn;

            3.若该区间已贴海报,则pushdown2个儿子,标记当前dat[k]= 0, 递归给2个儿子


读取海报标记的问题:

voidsearch (int k)

从树根往下递归,用ans记录节点张贴海报序号不重复 的个数。输出答案。


算法步骤:

区间离散化,包括排序

初始化线段树

更新区间

读取可见海报张数


算法复杂度:

O(n + 2nlog2n + 2n) ,即O(nlogn)

O(n)

O(nlogL) O(nlog2n),即O(nlogn)

O(n)

总的复杂度为:O(nlogn)


源程序:

#include <stdio.h>
#include <string.h>
#include <algorithm>
using std::sort;

int n,ans;
const int maxn = 20001;
int POST[maxn][2];
bool vis[maxn];

struct line {
	int val;
	int num;
}Line[maxn];

int dat[4*maxn];

bool cmp(const line & a , const line & b) {
	return a.val < b.val;
}

void init(int cnt) {
	int t = cnt;
	int k = 1;
	while( k < t)
		k *= 2;
	for(int i = 1 ; i <= 2*k - 1 ; i ++)
		dat[i] = 0;
}

void update(int fr , int to , int L , int R , int k , int po) {
	if(to < L || fr > R)
		return;
	else if(fr <= L && to >= R)
		dat[k] = po;
	else
	{
		if(dat[k] > 0)
		{
			dat[2*k] = dat[2*k+1] = dat[k];
			dat[k] = 0;
		}
		int mid = (L+R)>>1;
		update(fr,to,L,mid,2*k,po);
		update(fr,to,mid+1,R,2*k+1,po);
	}
}

void search(int k) {
	if(dat[k])
	{
        if(!vis[dat[k]])
		{
            vis[dat[k]] = true;
            ans++;
        }
        return ;
      }
      search(2*k);
      search(2*k+1);
}

int main() {
	int C;
	scanf("%d",&C);
	while(C--)
	{
		// 输入并离散化 
		scanf("%d",&n);
		for(int i = 0 ; i < n ; i ++)
		{
			scanf("%d%d",&POST[i][0],&POST[i][1]);
			Line[2*i].num = -(i+1);
			Line[2*i].val = POST[i][0];
			Line[2*i+1].num = i+1;
			Line[2*i+1].val = POST[i][1];
		}
		sort(Line,Line+2*n,cmp);
		
		int tmp = Line[0].val, cnt = 1;
		for(int i = 0 ; i < 2*n ; i ++)
		{
			if(Line[i].val != tmp)
			{
				tmp = Line[i].val;
				cnt ++;
			}
			if(Line[i].num < 0)
				POST[-Line[i].num-1][0] = cnt;
			else
				POST[Line[i].num-1][1] = cnt;
		}
		
		init(cnt);
		for(int i = 0 ; i < n ; i ++)
			update(POST[i][0],POST[i][1],1,cnt,1,i+1);
			
		memset(vis,0,sizeof(vis));
		ans = 0;
		
		search(1);
		printf("%d\n",ans);
	}
	return 0;
}



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值