原创大佬的博客线段树进阶-染色问题
建树
线段树的精髓就是利用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;
}