1. 程式人生 > >poj 2528 Mayor's posters(離散化+線段樹)

poj 2528 Mayor's posters(離散化+線段樹)

題意:

市長競選,每個市長都往牆上貼海報,海報之間彼此可以覆蓋,給出貼上順序和每個海報的起點和終點,問最後又多少海報時可見的。

剛接觸離散化,先寫一寫本人的理解:

如果原資料太大,建樹會超記憶體。因此可以先對這些數排序,然後將它們對映成排序後的序號。比如在該題中,[1,4],[2,6],[8,10],[3,4],[7,10]; 先對這些區間端點去重(離散化要注意這一點,可以節省空間)排序後得  1,2,3,4,6,7,8,10 ;那麼可以將這些數對映成其對應編號:

1        2        3        4        6        7         8        10

1        2        3        4        5        6         7         8

那麼原區間就變成[1,4],[2,5],[7,8],[3,4],[6,8]; 這樣建樹只需要 8的空間,按區間端點數值建樹需要10的空間。。如果資料更大,經過離散後會大大節省空間。

思路:

由於wall最大是1000 0000,直接用線段端點座標建樹肯定超記憶體。所以就像上面說的先離散化,再建樹。之後,再從第一張開始貼,每貼一張就去更新相應區間的kind值。最後遞迴統計有多少個不同的kind值即可。

#include <stdio.h>
#include <iostream>
#include <map>
#include <set>
#include <stack>
#include <vector>
#include <math.h>
#include <string.h>
#include <queue>
#include <string>
#include <stdlib.h>
#include <algorithm>
#define LL long long
#define _LL __int64
#define eps 1e-12
#define PI acos(-1.0)
#define C 240
#define S 20
using namespace std;

const int maxn = 100010;

int n;
int np;

struct node
{
	int l,r;
	int val;
}post[10010],tree[80010];

int tmp[100010];
int f[10000010]; //對映函式
int vis[20010]; //用於統計海報數目
int cnt;

void build(int v, int l, int r)
{
	tree[v].l = l;
	tree[v].r = r;
	tree[v].val = -1;
	if(l == r)
		return;
	int mid = (l+r) >> 1;
	build(v*2,l,mid);
	build(v*2+1,mid+1,r);
}

void update(int v, int l, int r, int val)
{
	if(tree[v].l == l && tree[v].r == r)
	{
		tree[v].val = val;
		return;
	}

	if(tree[v].val != -1)
	{
		int mid = (tree[v].l + tree[v].r) >> 1;
		update(v*2,tree[v].l,mid,tree[v].val);
		update(v*2+1,mid+1,tree[v].r,tree[v].val);
		tree[v].val = -1;
	}

	int mid = (tree[v].l + tree[v].r)>>1;
	if(r <= mid)
		update(v*2,l,r,val);
	else if(l > mid)
		update(v*2+1,l,r,val);
	else
	{
		update(v*2,l,mid,val);
		update(v*2+1,mid+1,r,val);
	}
}

int query(int v, int l, int r)
{
	if(tree[v].l == l && tree[v].r == r)
	{
		if(tree[v].val != -1 && !vis[tree[v].val])
		{
			vis[tree[v].val] = 1;
			return 1;
		}
		else if(tree[v].val == -1)
		{
			int mid = (tree[v].l + tree[v].r) >> 1;
			return query(v*2,tree[v].l,mid) + query(v*2+1,mid+1,tree[v].r);
		}
		else
			return 0;
	}
	int mid = (tree[v].l + tree[v].r) >> 1;
	if(r <= mid)
		return query(v*2,l,r);
	else if(l > mid)
		return query(v*2+1,l,r);
	else
		return query(v*2,l,mid) + query(v*2+1,mid+1,r);
}

int main()
{
	int test;
	scanf("%d",&test);

	while(test--)
	{
		scanf("%d",&n);
		np = 0;
		for(int i = 1; i <= n; i++)
		{
			scanf("%d %d",&post[i].l,&post[i].r);
			tmp[np++] = post[i].l;
			tmp[np++] = post[i].r;
		}

		sort(tmp,tmp+np);
		//座標離散化並去重
		int k = 1;
		for(int i = 1; i < np; i++)
		{
			while(tmp[i] == tmp[k-1])
			{
				i++;
				if(i == np)
					break;
			}
			if(i == np)
				break;
			tmp[k++] = tmp[i];
		}
		np = k;
		for(int i = 0; i < np; i++)
		{
			f[tmp[i]] = i + 1;
		}
		build(1,1,np);
		for(int i = 1; i <= n; i++)
		{
			update(1,f[post[i].l],f[post[i].r],i);
		}
		memset(vis,0,sizeof(vis));
		int ans = query(1,1,np);
		printf("%d\n",ans);
		
	}
	return 0;
}


1、  線段樹是二叉樹,且必定是平衡二叉樹,但不一定是完全二叉樹。

2、  對於區間[a,b],令mid=(a+b)/2,則其左子樹為[a,mid],右子樹為[mid+1,b],當a==b時,該區間為線段樹的葉子,無需繼續往下劃分。

3、  線段樹雖然不是完全二叉樹,但是可以用完全二叉樹的方式去構造並存儲它,只是最後一層可能存在某些葉子與葉子之間出現“空葉子”,這個無需理會,同樣給空葉子按順序編號,在遍歷線段樹時當判斷到a==b時就認為到了葉子,“空葉子”永遠也不會遍歷到。

4、  之所以要用完全二叉樹的方式去儲存線段樹,是為了提高在插入線段和搜尋時的效率。用p*2,p*2+1的索引方式檢索p的左右子樹要比指標快得多。

5、線段樹的精髓是,能不往下搜尋,就不要往下搜尋,儘可能利用子樹的根的資訊去獲取整棵子樹的資訊。如果在插入線段或檢索特徵值時,每次都非要搜尋到葉子,還不如直接建一棵普通樹更來得方便。