做了这道题才稍微理解了线段树中的两个重要操作,即PushDown传递标记和合并子区间这两个操作,也理解了标记的用途和正确使用方式。
总结一下,正常情况下线段树有两个重要数据,一个是信息一个是标记,信息要支持区间合并,而标记则要支持区间划分(下放操作PushDown)。当我们更新区间的时候,当找到最大的被完全覆盖的区间时,只需更新这个区间的信息,然后做一个标记,表示这个区间的子区间有待于更新,当我们发现一个区间并没有被完全覆盖而需要用到它的子区间时,我们要考虑这个标记,即考虑这个区间的子区间需不需要被更新,如果存在标记就先PushDown一下。
有些题目比如poj2528,信息和标记可用一个量表示,即这个量即支持区间合并又支持下放。
可参考杨弋的线段树论文...做完这道题才稍微看懂这篇论文...= =|||,想深入学习就去做notonlysuccess的线段树专辑...
ps: 上面的部分名词是我瞎编的...反正我自己觉得挺形象...
代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
#define N 100005
int Col[N<<2],Cnt[N<<2];
void Build()
{
Cnt[1]=1,Col[1]=1;
}
void PushDown(int rt)
{
Cnt[rt<<1]=Cnt[rt<<1|1]=1<<(Col[rt]-1);
Col[rt<<1]=Col[rt<<1|1]=Col[rt];
Col[rt]=-1;
}
void Update(int col,int L,int R,int l,int r,int rt)
{
if(L<=l&&r<=R)
{
Col[rt]=col;
Cnt[rt]=1<<(col-1);
return ;
}
if(L>r||R<l)
return ;
if(Col[rt]!=-1)
PushDown(rt);
int mid=(l+r)>>1;
Update(col,L,R,lson);
Update(col,L,R,rson);
Cnt[rt]=Cnt[rt<<1]|Cnt[rt<<1|1];
}
int Query(int L,int R,int l,int r,int rt)
{
if(L<=l&&r<=R)
{
return Cnt[rt];
}
if(L>r||R<l)
return 0;
if(Col[rt]!=-1)
PushDown(rt);
int mid=(l+r)>>1;
return Query(L,R,lson)|Query(L,R,rson);
}
int main()
{
int l,t,o,i,a,b,c,tmp,ans;
char order;
Build();
scanf("%d %d %d",&l,&t,&o);
while(o--)
{
getchar();
scanf("%c",&order);
if(order=='C')
{
scanf("%d %d %d",&a,&b,&c);
if(b<a)
swap(a,b);
Update(c,a,b,1,l,1);
}
else
{
scanf("%d %d",&a,&b);
if(b<a)
swap(a,b);
tmp=Query(a,b,1,l,1);
ans=0;
while(tmp)
{
if(tmp&1)
ans++;
tmp>>=1;
}
printf("%d\n",ans);
}
}
return 0;
}