天天看點

線段樹之六

#include <iostream>        //poj 2777 Count Color  線段樹+位運算      
using namespace std;      
struct node      
{      
int l,r,color;        //采用位運算代替用一個vis[]數組來表示哪些顔色存在或不存在,這樣既省空間也省時間      
}tree[300000];      
int c,count;      
void build(int s,int t,int num)      
{      
tree[num].l=s;tree[num].r=t;      
tree[num].color=1;      
if(s!=t)      
{      
int mid=(s+t)/2;      
build(s,mid,num*2);      
build(mid+1,t,num*2+1);      
}      
}      
int calculate(int p)        //計算出p值所含的顔色種類數      
{      
int s=0;      
while(p>0)      
{      
s++;      
p-=(p&(-p));      
}      
return s;      
}      
void modify(int a,int b,int num)      
{      
//因為在插入一個區間時,當插入區間等于目前區間時,目前區間的顔色      
//數目就變為了1,目前區間的孩子區間也全都變為1,是以不須一直更新到最底層      
//等到重新插入另一個區間時再來更新孩子區間的顔色數,如果一直更新到最底層,則會TLE      
if(calculate(tree[num].color)==1&&tree[num].l<tree[num].r)        //tree[num].l<tree[num].r,如果是葉子節點,則無需更新左右子樹      
{      
tree[num*2].color=tree[num*2+1].color=tree[num].color;    //更新孩子區間的顔色數,如果該區間的顔色數不是1的話,則它的孩子區間也必已更新了      
}      
if(tree[num].l==a&&tree[num].r==b)    //隻有當插入區間等于目前區間時,才不用去更新它的孩子區間,每次插入時上面的if語句已經更新了      
tree[num].color=c;      
else      
{      
if(b<=tree[num*2].r)      
modify(a,b,num*2);      
else if(a>=tree[num*2+1].l)      
modify(a,b,num*2+1);      
else      
{      
int mid=(tree[num].l+tree[num].r)/2;     //注意不是 mid=(a+b)/2;      
modify(a,mid,num*2);      
modify(mid+1,b,num*2+1);      
}      
tree[num].color=tree[num*2].color | tree[num*2+1].color;      
}      
}      
void query(int a,int b,int num)      
{      
if(calculate(tree[num].color)==1&&tree[num].l<tree[num].r)      
{      
tree[num*2].color=tree[num*2+1].color=tree[num].color;      
}      
if(a==tree[num].l&&b==tree[num].r)      
count=count | tree[num].color;      
else      
{      
if(b<=tree[num*2].r)      
query(a,b,num*2);      
else if(a>=tree[num*2+1].l)      
query(a,b,num*2+1);      
else      
{      
int mid=(tree[num].l+tree[num].r)/2;        //注意不是 mid=(a+b)/2;      
query(a,mid,num*2);      
query(mid+1,b,num*2+1);      
}      
}      
}      
int main()      
{      
int L,T,O,a,b;char ch;      
cin>>L>>T>>O;      
build(1,L,1);      
while(O--)      
{      
cin>>ch;      
if(ch=='C')      
{      
scanf("%d%d%d",&a,&b,&c);      
if(a>b)swap(a,b);      
c=1<<c-1;            //相當于c=1<<(c-1)      
modify(a,b,1);      
}      
else      
{      
count=0;      
scanf("%d%d",&a,&b);      
if(a>b)swap(a,b);      
query(a,b,1);      
printf("%d\n",calculate(count));      
}      
}      
return 0;      
}      
//采用位運算可以大大節約時間      
//顔色的表示方法。題目上有說明“T<=30”,顔色的種類不多(2<<T在int 範圍内),是以使用二進制來存儲顔      
//色的資訊,第n位上有 1 則表示是顔色n。具體說來,就是用1表示顔色1,10表示顔色2,100表示顔色3, 1001      
//= 9表示 1 , 4  這2種顔色 ……依次類推,這樣表示有一種好處,要統計出 不重複 的顔色的種類,假設某      
//線段上原來有顔色 2 ,現在在某部分塗上顔色 2 ,那麼對 2 和 2 進行  或 |  運算, 還是 10 , 如果再      
//在某部分塗上顔色4  (1000),與原來的 10 進行 或 | 運算,得到 1010 ,在這個二進制數找出有幾個1,      
//就可以求出這條線段包含有幾種不重複的顔色。有兩個 1,說明有兩種顔色( 2 和 4)。如何在二進制數找出      
//有幾個 1 ,  可以這樣統計 1 的個數:      
//int calculate(int p)      
//{      
//    int s=0;      
//    while(p>0)      
//    {      
//        s++;      
//        p-=(p&(-p));        //每次都把右邊起的第1個 1 變成 0      
//    }      
//    return s;      
//}      
//比如10100, 10100&(-10100)=100,  10100-100=10000      
//10000&(-10000)=10000, 10000-10000=0   至此循環終止, s=2, 表示有兩個1