poj2528(离散化 + 染色)

<pre name="code" class="cpp">/*****************************************
Author      :Crazy_AC(JamesQi)
Time        :2016
File Name   :
*****************************************/
// #pragma comment(linker, "/STACK:1024000000,1024000000")
#include <iostream>
#include <algorithm>
#include <iomanip>
#include <sstream>
#include <string>
#include <stack>
#include <queue>
#include <deque>
#include <vector>
#include <map>
#include <set>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <cstdlib>
#include <climits>
using namespace std;
#define MEM(x,y) memset(x, y,sizeof x)
#define pk push_back
#define lson rt << 1
#define rson rt << 1 | 1
typedef long long LL;
typedef unsigned long long ULL;
typedef pair<int,int> ii;
typedef pair<ii,int> iii;
const double eps = 1e-10;
const int inf = 1 << 30;
const int INF = 0x3f3f3f3f;
const int MOD = 1e9 + 7;
const int maxn = 42222;
struct Node {
	int l, r;
	int color;
	int clear;
}p[maxn<<2];
int e[maxn][2];
int Hash[10000000 + 11];
int a[maxn];
void build(int rt,int L,int R) {
	p[rt].l = L, p[rt].r = R;
	p[rt].color = 0, p[rt].clear = -1;
	if (L == R) return;
	int mid = (L + R) >> 1;
	build(lson, L, mid);
	build(rson, mid + 1, R);
}
void down(int rt) {
	if (p[rt].clear != -1) {
		p[lson].color = p[rson].color = p[rt].clear;
		p[lson].clear = p[rson].clear = p[rt].clear;
		p[rt].clear = -1;
	}
}
void up(int rt) {
	if (p[lson].color == 0 && p[rson].color == 0) p[rt].color = 0;
	if (p[lson].color == -1 || p[rson].color == -1) p[rt].color = -1;
	if (p[lson].color > 0 && p[lson].color > 0) {
		if (p[lson].color == p[rson].color) p[rt].color = p[rson].color;
		else p[rt].color = -1;
	}
	else p[rt].color = p[lson].color | p[rson].color;
}
void updata(int rt,int L,int R,int col) {
	if (L <= p[rt].l && p[rt].r <= R) {
		p[rt].color = col;
		p[rt].clear = col;
		return ;
	}
	down(rt);
	int mid = (p[rt].l + p[rt].r) >> 1;
	if (L <= mid) updata(lson, L, R, col);
	if (R > mid) updata(rson, L, R, col);
	up(rt);
}
int cnt[maxn];
void Query(int rt,int L, int R) {
	if (p[rt].color > 0) {
		cnt[p[rt].color] = 1;
		return ;
	}
	if (p[rt].color == 0) return ;
	down(rt);
	if (p[rt].color == -1) { 
		Query(lson, L, R);
		Query(rson, L, R);
	}
}
int main()
{	
	// freopen("in.txt","r",stdin);
	// freopen("out.txt","w",stdout);
	int t, n;
	scanf("%d",&t);
	while(t--) {
		scanf("%d",&n);
		int top = 0;
		for (int i = 1; i <= n;++i) {
			scanf("%d%d",&e[i][0],&e[i][1]);
			a[++top] = e[i][0];
			a[++top] = e[i][1];
		}
		sort(a + 1,a + top + 1);
		Hash[a[1]] = 1;
		for (int i = 2;i <= top;++i) {
			if (a[i] == a[i-1]) Hash[a[i]] = Hash[a[i-1]];
			if (a[i] - a[i-1] == 1) Hash[a[i]] = Hash[a[i-1]] + 1;
			if (a[i] - a[i-1] > 1) Hash[a[i]] = Hash[a[i-1]] + 2;
		}
		build(1, 1, Hash[a[top]]);
		for (int i = 1;i <= n;++i) {
			updata(1, Hash[e[i][0]], Hash[e[i][1]], i);
			// printf("[%d, %d]->[%d, %d]\n", e[i][0], e[i][1], Hash[e[i][0]], Hash[e[i][1]]);
		}
		memset(cnt, 0,sizeof cnt);
		Query(1, 1, Hash[a[top]]);
		int num = 0;
		for (int i = 1;i <= n;++i)
			if (cnt[i]) num++;
		printf("%d\n", num);
	}
	return 0;
}


    若第i次在区间[ai , bi]染色,则把[ai , bi]的每一格都染色为i。后染的颜色覆盖先染的颜色。由于染色N次,定义一个标记数组tagcol,从数轴第一格开始检查,一直检查到最后,出现过得颜色则记录到tagcol,最后统计tagcol中不同颜色的个数,就是所求。 

       数据规模太大,必定TLE。

 

应该用线段树去求解,这题只是线段树的入门水题,不懂线段树的同学去找一些相关资料大概学习一下,然后看一下我代码中注释,这题就有思路做了。我推荐你们去看看“浙江大学acm校队”的关于线段树的PPT,里面有线段树从入门到精通的案例,也有涉及离散化的介绍,百度搜就有了。

然后我这里补充几点线段树的知识,网上关于线段树的资料很多有误导。

 

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、线段树的精髓是,能不往下搜索,就不要往下搜索,尽可能利用子树的根的信息去获取整棵子树的信息。如果在插入线段或检索特征值时,每次都非要搜索到叶子,还不如直接建一棵普通树更来得方便。

 

但是这题单纯用线段树去求解一样不会AC,原因是建立一棵[1,1QW]的线段树,其根系是非常庞大的,TLE和MLE是铁定的了。所以必须离散化。

通俗点说,离散化就是压缩区间,使原有的长区间映射到新的短区间,但是区间压缩前后的覆盖关系不变。举个例子:

有一条1到10的数轴(长度为9),给定4个区间[2,4] [3,6] [8,10] [6,9],覆盖关系就是后者覆盖前者,每个区间染色依次为 1 2 3 4。

现在我们抽取这4个区间的8个端点,2 4 3 6 8 10 6 9

然后删除相同的端点,这里相同的端点为6,则剩下2 4 3 6 8 10 9

对其升序排序,得2 3 4 6 8 9 10

然后建立映射

2     3     4     6     8     9   10 

↓     ↓      ↓     ↓     ↓     ↓     ↓

1     2     3     4     5     6     7

那么新的4个区间为 [1,3] [2,4] [5,7] [4,6],覆盖关系没有被改变。新数轴为1到7,即原数轴的长度从9压缩到6,显然构造[1,7]的线段树比构造[1,10]的线段树更省空间,搜索也更快,但是求解的结果却是一致的。

 

离散化时有一点必须要注意的,就是必须先剔除相同端点后再排序,这样可以减少参与排序元素的个数,节省时间。

 

附:海报张数上限为10000,即其端点映射的新数轴长度最多为20000。因此建立长度为1QW的离散数组dis时,可以使用unsigned short类型,其映射值最多为20000,这样可以节约空间开销。

/*****************************************
Author      :Crazy_AC(JamesQi)
Time        :2015
File Name   :
*****************************************/
// #pragma comment(linker, "/STACK:1024000000,1024000000")
#include <iostream>
#include <algorithm>
#include <iomanip>
#include <sstream>
#include <string>
#include <stack>
#include <queue>
#include <deque>
#include <vector>
#include <map>
#include <set>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <stdlib.h>
#include <limits.h>
using namespace std;
#define MEM(a,b) memset(a,b,sizeof a)
typedef long long LL;
typedef unsigned long long ULL;
typedef pair<int,int> ii;
const int inf = 1 << 30;
const int INF = 0x3f3f3f3f;
const int MOD = 1e9 + 7;
inline int Readint(){
	char c = getchar();
	while(!isdigit(c)) c = getchar();
	int x = 0;
	while(isdigit(c)){
		x = x * 10 + c - '0';
		c = getchar();
	}
	return x;
}

class LineTree_Node{
public:
	int s,e;
	int col;
	LineTree_Node():s(0),e(0),col(0){}
};
class solve{
public:
	solve(int n):N(n){
		Initial();
		Input();
		CreatLineTree(1,Maxp,1);
		Solvetion();
	}
	~solve(){
		for (int i = 1;i <= N;i++)
			delete[] reg[i];
		delete[] ep;
		delete[] dis;
		delete[] tagcol;
		delete[] LT;
	}
	void Initial(void);//初始化并申请空间
	void Input(void);//输入
	void CreatLineTree(int sp,int tp,int p);//构造[sp,tp]线段树
	void Solvetion(void);//插入区间,统计颜色
	void Insert(int a,int b,int p,int color);
	void DFS(int p);
protected:
	int N;
	int Maxp;
	LineTree_Node* LT;//线段树用
	int** reg;//输入的时候用
	int *ep;
	unsigned short *dis;//hash
	bool *tagcol;//标记
	int cnt;//记数
};
void solve::Initial(void){
	cnt = 0;
	reg = new int*[N + 1];
	for (int i = 1;i <= N;i++)
		reg[i] = new int[2];
	ep = new int[2 * N + 1];
	dis = new unsigned short[10000000];
	memset(dis,0,sizeof(unsigned short)*(10000000));
	tagcol = new bool[N + 1];
	memset(tagcol,false,sizeof(bool)*(N + 1));
	return;
}
void solve::Input(void){
	int p = 0;
	/*dis暂时作为标记数组*/
	for (int i = 1;i <= N;i++){
		scanf("%d%d",&reg[i][0],&reg[i][1]);
		if (dis[reg[i][0]] == 0){
			ep[p++] = reg[i][0];
			dis[reg[i][0]] = 1;
		}
		if (dis[reg[i][1]] == 0){
			ep[p++] = reg[i][1];
			dis[reg[i][1]] = 1;
		}
	}
	/*离散化*/
	sort(ep,ep + p);
	unsigned short hash = 0;
	/*hash <= 20000,所以可以用unsigned short来剩空间*/
	for (int j = 0;j < p;j++)
		dis[ep[j]] = ++hash;
	Maxp = hash;
	/*线段树申请空间*/
	LT = new LineTree_Node[4 * Maxp + 1];
	return;
}
void solve::CreatLineTree(int sp,int tp,int p){
	LT[p].s = sp;
	LT[p].e = tp;
	if (sp == tp) return;
	int mid = (sp + tp) >> 1;
	CreatLineTree(sp,mid,p << 1);
	CreatLineTree(mid + 1,tp,p << 1 | 1);
	return ;
}
void solve::Solvetion(void){
	for (int i = 1;i <= N;i++)
		Insert(dis[reg[i][0]],dis[reg[i][1]],1,i);
	DFS(1);
	printf("%d\n",cnt);
	return ;
}
void solve::Insert(int a,int b,int p,int color){
	if (b < LT[p].s || a > LT[p].e) return;
	if (a <= LT[p].s && b >= LT[p].e){
		LT[p].col = color;
		return ;
	}
	if (LT[p].col >= 0){
		LT[p << 1].col = LT[p << 1 | 1].col = LT[p].col;
		LT[p].col = -1;
	}
	Insert(a,b,p << 1,color);
	Insert(a,b,p << 1 | 1,color);
	return ;
}
void solve::DFS(int p){
	if (LT[p].col == 0) return;
	if (LT[p].col > 0){
		if (!tagcol[LT[p].col]){
			tagcol[LT[p].col] = true;
			cnt++;
		}
		return ;
	}
	if (LT[p].col == -1){
		DFS(p << 1);
		DFS(p << 1 | 1);
	}
	return ;
}
int main()
{	
	// freopen("in.txt","r",stdin);
	// freopen("out.txt","w",stdout);
	int test;
	test = Readint();
	for (int i = 1;i <= test;++i){
		int n;
		n = Readint();
		solve woca(n);
	}
	return 0;
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值