題目鏈接:https://www.luogu.org/problem/P1558
題目背景
阿寶上學了,今天老師拿來了一塊很長的塗色板。
題目描述
色板長度爲L,L是一個正整數,所以我們可以均勻地將它劃分成L塊1釐米長的小方格。並從左到右標記爲1, 2, … L。
現在色板上只有一個顏色,老師告訴阿寶在色板上只能做兩件事:
“C A B C” 指在A到 B 號方格中塗上顏色 C。
“P A B” 指老師的提問:A到 B號方格中有幾種顏色。
學校的顏料盒中一共有 T 種顏料。爲簡便起見,我們把他們標記爲 1, 2, … T. 開始時色板上原有的顏色就爲1號色。 面對如此複雜的問題,阿寶向你求助,你能幫助他嗎?
輸入格式
第一行有3個整數 L (1 <= L <= 100000), T (1 <= T <= 30) 和 O (1 <= O <= 100000)。 在這裏O表示事件數。
接下來 O 行, 每行以 “C A B C” 或 “P A B” 得形式表示所要做的事情(這裏 A, B, C 爲整數, 可能A> B,這樣的話需要你交換A和B)
輸出格式
對於老師的提問,做出相應的回答。每行一個整數。
輸入輸出樣例
輸入 #1
2 2 4
C 1 1 2
P 1 2
C 2 2 2
P 1 2
輸出 #1
2
1
題意: 兩個操作,一個區間可覆蓋塗色;一個區間詢問有多少中 顏色。
思路:
- 看到顏色覆蓋,以爲是區間染色問題,不過如果這個題的塗色的是每個點,而不是兩點之間的區間,這樣就比較好解決。
- 一種思路,改變顏色時,如果當前節點的兩個子節點不同,則說明還未到最低端,賦爲-1。如果兩個子節點相同,說明該區間內的顏色相同,則隨便一個賦值給當前節點。仔細思考一下就明白。
- 另一個看的其他大佬的思路,題目給的顏色只有30種,所以將顏色狀態壓縮,轉爲2的k次方,既每一種顏色代表2的幾次方。這樣處理就可以用線段樹進行單點修改,詢問的時候只需或起來。
- 位運算小知識:
1<<(a-1)爲構造一個只有a位置爲1,其他爲0的二進制數。
或在二進制中就相當於“加”,保存1,就不用多說。在上面狀態壓縮的鏈接中有詳細證明。
下面是兩種思路的代碼:
普通解法ac代碼:
#include<stdio.h>
#include<algorithm>
#include<string.h>
#define ll long long
using namespace std;
const int maxn=1e5+7;
const ll INF=1e9;
ll sum[maxn*4],add[maxn*4];
ll vis[maxn];
void pushdown(ll root)
{
if(add[root])
{
add[root<<1]=add[root];
add[root<<1|1]=add[root];
sum[root<<1]=add[root];
sum[root<<1|1]=add[root];
add[root]=0;
}
}
void change(ll root,ll l,ll r,ll ql,ll qr,ll k)
{
if(ql<=l&&qr>=r)
{
sum[root]=add[root]=k;
return ;
}
ll mid=(l+r)>>1;
pushdown(root);
if(ql<=mid) change(root<<1,l,mid,ql,qr,k);
if(qr>mid) change(root<<1|1,mid+1,r,ql,qr,k);
if(sum[root<<1]==sum[root<<1|1])
sum[root]=sum[root<<1];
else
sum[root]=-1;
}
void q(ll root,ll l,ll r,ll ql,ll qr)
{
if(ql<=l&&qr>=r)
{
if(sum[root]!=-1)
{
vis[sum[root]]=1;
return ;
}
}
pushdown(root);
ll mid=(l+r)>>1;
if(ql<=mid) q(root<<1,l,mid,ql,qr);
if(qr>mid) q(root<<1|1,mid+1,r,ql,qr);
}
int main()
{
ll n,t,o;
scanf("%lld%lld%lld",&n,&t,&o);
for(int i=1;i<=n;i++)
change(1,1,n,i,i,1);
char a[2];
for(int i=1;i<=o;i++)
{
scanf("%s",a);
if(a[0]=='C')
{
ll A,B,C;
scanf("%lld%lld%lld",&A,&B,&C);
if(A>B)
swap(A,B);
change(1,1,n,A,B,C);
}
else
{
ll A,B;
scanf("%lld%lld",&A,&B);
if(A>B)
swap(A,B);
ll num=0;
memset(vis,0,sizeof(vis));
q(1,1,n,A,B);
for(int i=1;i<=t;i++)
{
if(vis[i])
num++;
}
printf("%lld\n",num);
}
}
return 0;
}
位運算解法ac代碼:
#include<stdio.h>
#include<algorithm>
#include<string.h>
#define ll long long
using namespace std;
const int maxn=2e5+7;
const ll INF=1e9;
ll sum[maxn*4],add[maxn*4];
void build(ll root,ll l,ll r)
{
add[root]=0;
if(l==r)
{
sum[root]=1;
return;
}
ll mid=(l+r)>>1;
build(root<<1,l,mid);
build(root<<1|1,mid+1,r);
sum[root]=sum[root<<1]|sum[root<<1|1];
}
void pushdown(ll root)
{
if(add[root])
{
add[root<<1]=add[root];
add[root<<1|1]=add[root];
sum[root<<1]=add[root];
sum[root<<1|1]=add[root];
add[root]=0;
}
}
void change(ll root,ll l,ll r,ll ql,ll qr,ll k)
{
if(ql<=l&&qr>=r)
{
sum[root]=add[root]=k;
return ;
}
ll mid=(l+r)>>1;
pushdown(root);
if(ql<=mid) change(root<<1,l,mid,ql,qr,k);
if(qr>mid) change(root<<1|1,mid+1,r,ql,qr,k);
sum[root]=sum[root<<1]|sum[root<<1|1];
}
ll q(ll root,ll l,ll r,ll ql,ll qr)
{
if(ql<=l&&qr>=r)
return sum[root];
ll mid=(l+r)>>1;
pushdown(root);
ll ans=0;
if(ql<=mid) ans|=q(root<<1,l,mid,ql,qr);
if(qr>mid) ans|=q(root<<1|1,mid+1,r,ql,qr);
return ans;
}
int main()
{
ll n,t,m;
scanf("%lld%lld%lld",&n,&t,&m);
build(1,1,n);
for(int i=1;i<=m;i++)
{
char a[2];
scanf("%s",a);
if(a[0]=='C')
{
ll A,B,C;
scanf("%lld%lld%lld",&A,&B,&C);
if(A>B)
swap(A,B);
ll cnt=1<<(C-1);
change(1,1,n,A,B,cnt);
}
else
{
ll A,B;
scanf("%lld%lld",&A,&B);
if(A>B)
swap(A,B);
ll ans=0,num=0;
ans=q(1,1,n,A,B);
//printf("ans: %lld\n",ans);
for(int i=1;i<=t;i++)
{
if(ans&(1<<(i-1)))
num++;
}
printf("%lld\n",num);
}
}
}