pku2777 线段树(染色问题)

  先来谈谈lazy思想。做了这么多的线段树,应该总结一下,lazy是一个很经典的思
想。所谓lazy,就是懒惰,每次不想做太多,只要插入的区间完全覆盖了当前结点所管
理的区间就不再往下做了,在当前结点上打上一个lazy标记,然后直接返回。下次如果
遇到当前结点有lazy标记的话,直接传递给两个儿子,自己的标记清空。这样做肯定是
正确的。我们以染色为例,可以这样想,如果当前结点和它的子孙都有lazy标记的话,
必定是子孙的先标记,因为如果是自己先标记,那么在访问子孙的时候,必定会将自己
的标记下传给儿子,而自己的标记必定会清空,那么lazy标记也就不存在了。所以可以
肯定,当前的lazy标记必定覆盖了子孙的,所以直接下传即可,不需要做任何判断。当
然,这是染色问题,是直接赋值的,如果像pku 3468那样,每次是区间加和,则传递标
记的时候不能简单的赋值,必须累加,这是显而易见的。

 一看到数据量就可以首先确定是线段树了,经典的区间染色问题,涉及到区间的
更新和询问,和pku 3468 类似,巧妙运用lazy思想。就是每次更新区间段的时候延迟
更新,只是在完全覆盖的区间打上一个lazy标记。这题的询问是求区间段中不同颜色的
数量,因为颜色数不多只有30种,可以巧妙运用二进制位运算,用一个int就可以表示
当前区间段的颜色情况。比如1001表示有两种颜色,如果左子树的当前颜色情况是101
,而右子树的颜色情况是011,那么父亲的颜色情况就是两者的位或,这样就可以避免
掉重复的情况。

#include<stdio.h>
#define N 100010
struct node{
int l,r,c;
}p[N
*3];
int flag,count;
void bulid(int k,int s,int t)
{
int kl,kr,mid;
p[k].r
=t;p[k].l=s;
p[k].c
=1;
if(t==s)return ;

mid
=(s+t)>>1;kl=k<<1;kr=kl+1;
bulid(kl,s,mid);
bulid(kr,mid
+1,t);

}
void updata(int k,int s,int t,int v)
{
int kr,kl,mid;
if(p[k].c==v)
return ;
if(s<=p[k].l&&t>=p[k].r)
p[k].c
=v;
else {
mid
=(p[k].r+p[k].l)>>1;kl=k<<1;kr=kl+1;
if(p[k].c!=-1)
{
p[kr].c
=p[kl].c=p[k].c;
p[k].c
=-1;
}
if(s<=mid)updata(kl,s,t,v);
if(t>mid)updata(kr,s,t,v);
}
}
void query(int k,int s,int t)
{
int kr,kl,mid;
if(p[k].c>0)
{
int t=p[k].c;
if((flag&(1<<t))==0)//由于颜色只有30种,所以可以用位运算来标记状态
{ //注意这里的移位操作,flag的32个位上的1和0分别表示该位的位置代表的
// 颜色是否存在过,先将1左移t个位,则只有第t+1个位上的数字为1,其余为0,
count
++; // 当与flag进行位与运算等于0时,则表示所对应的颜色没出现过
flag
|=1<<t; //同样的,若出现过,则用flag在该位上标记一下
}
return ;
}
mid
=(p[k].r+p[k].l)>>1;kl=k<<1;kr=kl+1;
if(s<=mid) query(kl,s,t);
if(t>mid) query(kr,s,t);
}
int main()
{
int n,m,a,b,x,t;
char ch[2];
while(scanf("%d %d %d",&n,&t,&m)!=EOF)
{
bulid(
1,1,n);
while(m--)
{
scanf(
"%s",ch);
if(ch[0]=='C')
{
scanf(
"%d %d %d",&a,&b,&x);
if(a>b) { t=a;a=b;b=t;}
updata(
1,a,b,x);
}
else {
flag
=count=0;
scanf(
"%d %d",&a,&b);
if(a>b){t=a;a=b;b=t;}
query(
1,a,b);
printf(
"%d\n",count);
}
}
}
return 0;
}
原文地址:https://www.cnblogs.com/nanke/p/2032206.html