线段树-染色问题 模板&POJ 2777题解

原创大佬的博客线段树进阶-染色问题

建树

线段树的精髓就是利用lazy数组,可以保证不遍历到子节点就可以获得区域的情况。那么染色也要利用这个性质,对于一段区间,我们想要在上层节点上面表示出来,那么我们可以设置3个变量:-1表示当前区段有多种颜色,具体有多少种我们不用管。0表示当前区域未染色。正整数表示当前区域染了单一染色,并且颜色号是这个正整数。
在这里插入图片描述
这是按照上面的要求写的线段树,-1表示这段有多种颜色,0表示这段没有染过色,正整数表示当前这段为单种颜色。这里的左边为1个1和1个0,而上面结点仍然是-1.因为默认把0也当作一种颜色了。可以节省代码,实际在查找的时候我们只需要排除0就可以得到正确答案了。
总之,这样建立的线段树可以表示区域染色的大致情况,而具体有多少种颜色需要在查询的时候进行操作。
关于建树,因为染色问题,所以有覆盖这种说法,后涂的颜色会覆盖先涂的,所以一般的原数据是什么都没有,也就是整个线段树的值都是0.这样初始化代码只需要将线段树初始化就可以了。

void build(){
	memset(tree,0,sizeof(tree));
}
//有这样的一种情况,初始时底层都有颜色 
int color=1;
void build(int id,int l,int r){
	if(l==r){
		tree[id]=color++;
		return; 
	}
	int mid=(l+r)>>1;
	build(id<<1,l,mid);
	build(id<<1|1,mid+1,r);
	tree[id]=-1;
}

区间染色的情况

void update(int id,int l,int r,int a,int b,int col){
	if(a<=l&&r<=b){
		lazy[id]=col;
		tree[id]=col;
		return;
	}
	pushdown(id);
	int mid=(l+r)>>1;
	if(a<=mid)update(id<<1,l,mid,a,b,col);
	if(b>mid)update(id<<1|1,mid+1,r,a,b,col);
	if(tree[id<<1]==tree[id<<1|1]){
		tree[id]=tree[id<<1|1];
	}
	else{
		tree[id]=-1;
	}
}

仍然需要利用二分来找到具体的区间,但是重点在于更新方式:进入刚开始的语句,说明此时道道的要么是整段的区间要么是叶子结点,所以直接更新为当前染色就行了,不需要管下面多少种颜色,反正全部被覆盖了,就只剩这一种。
而下面递归之后的回溯需要稍微考虑一下。有5种情况
1.区间内有只有一种颜色,即左右子树颜色值一样。那么父结点当然也要跟子结点值一样。
2.区间内什么颜色也没有,全部为0。
3.区间内颜色很杂,左右子树全部为-1。
4.区间内颜色很杂,但是左右子树是纯色,即值为正整数却不相同。
5.区间内颜色很杂,左右子树一个纯色一个杂色,即值一个为-1,一个为正整数。
这5种情况其实1、2、3一样,4、5一样。只要左右子树值一样,父结点直接赋值,0也一样正确,-1也一样,反正子区间很杂,那父区间肯定更杂了-1就行。4、5都是区间杂乱,直接-1就行。
这里可能会造成这样的困扰,例如左子树是纯色2,右子树无颜色为0。那么父结点应该为2啊,上面却写成-1了。其实没必要分这么细,因为是-1,所以查找的时候仍然向下查,左子树为2,记一种颜色,右子树为0,判定为0不加颜色。这样就解决了。节省了代码。

push函数的改动

既然是染色,但是我们仍然需要lazy数组,标记下面的区域全部为某种纯色,也是完全可以使用的。那么push函数就需要一定的改变,非常简单的改动。改成现在的模式就可以了:

void pushdown(int id){
	if(lazy[id]){//如果没有lazy标记则直接跳过
		lazy[id<<1]=lazy[id];
		lazy[id<<1|1]=lazy[id];
		tree[id<<1]=lazy[id];
		tree[id<<1|1]=lazy[id];
		lazy[id]=0;
	}
}

仍然利用上面的规则,仍然是二分向下查询。全局定义一个ans变量记录数量,当碰见0,说明下面没有颜色了,直接return。碰见正整数说明下面全是单色,ans++同时return。当碰见-1,说明下面还有多种颜色,二分继续向下查找。这样也是在原来线段树区间查询的基础上进行修改。

int ans=0;
void query(int id,int l,int r,int a,int b){
	if(a<=l&&r<=b){
		if(tree[id]==0){
			return;
		}
		if(tree[id]==-1){
			int mid=(l+r)>>1;
			if(a<=mid)query(id<<1,l,mid,a,b);
			if(b>mid)query(id<<|1,mid+1,r,a,b);
		}
		else{
			if(!color[tree[id]]){
				ans++;
				color[tree[id]]=true;
			}
		}
		return;
	}
	pushdown(id);//必须要进行下放 
	int mid=(l+r)>>1;
	if(a<=mid)query(id<<1,l,mid,a,b);
	if(b>mid)query(id<<1|1,mid+1,r,a,b);
} 

总结:重点是对于线段树的变形,而不是死记模板。

例题:POJ 2777 染色问题

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=100000;
int tree[maxn<<2],lazy[maxn<<2];
bool color[35];
int L,O,T;
int ans=0;
void init() {
    for (int i = 0; i < maxn * 4; i++) {
        tree[i] = 1;
    }
    memset(lazy, 0, sizeof(lazy));
    memset(color, 0, sizeof(color));
} 
void pushdown(int id){
	if(lazy[id]){
		tree[id<<1]=lazy[id];
		tree[id<<1|1]=lazy[id];
		lazy[id<<1|1]=lazy[id];
		lazy[id<<1]=lazy[id];
		lazy[id]=0;
	}
}
void update(int id,int l,int r,int a,int b,int c){
	if(a<=l&&r<=b){
		tree[id]=c;
		lazy[id]=c;
		return;
	}
	pushdown(id);
	int mid=(l+r)>>1;
	if(a<=mid)update(id<<1,l,mid,a,b,c);
	if(b>mid)update(id<<1|1,mid+1,r,a,b,c);
	if(tree[id<<1]==tree[id<<1|1]){
		tree[id]=tree[id<<1];
	}
	else{
		tree[id]=-1;
	}
}
void query(int id,int l,int r,int a,int b){
	if(a<=l&&r<=b){
		if(tree[id]==0)return;
		if(tree[id]==-1){
			pushdown(id);
			int mid=(l+r)>>1;
			if(a<=mid)query(id<<1,l,mid,a,b);
			if(b>mid)query(id<<1|1,mid+1,r,a,b);
		}
		else{
			if(color[tree[id]]==0){
				ans++;
				color[tree[id]]=1;
			}
		}
		return;
	}
	pushdown(id);
	int mid=(l+r)>>1;
	if(a<=mid)query(id<<1,l,mid,a,b);
	if(b>mid)query(id<<1|1,mid+1,r,a,b);
} 
int main(){
	while(~scanf("%d%d%d",&L,&T,&O)){
		init();
		char C[10];
		int a,b,c;
		for(int i=1;i<=O;i++){
			scanf("%s%d%d",C,&a,&b);
			if(a>b){
				swap(a,b);
			}
			if(C[0]=='C'){
				scanf("%d",&c);
				update(1,1,L,a,b,c);
			}
			else{
				ans=0;
				memset(color,0,sizeof(color));
				query(1,1,L,a,b);
				cout << ans << endl;
			}
		}	
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值