區域染色覆蓋問題
假設某大學有一面文化牆,各個學院都可以在上面塗色,要求塗色區域高必須和牆一樣,寬度任意但必須是整數(以米爲單位)。塗色可以覆蓋其他學院的塗色。現在若干個學院塗色之後最終這面牆上能看見多少種顏色。
這就是簡單的染色問題了。當然有很多種不同的說法,但整體上離不開這兩個問題:1.區域覆蓋。2.多種染色方式。既然是區域操作,那麼用線段樹是比較合理了。用數組也可以,但是數據規模稍微大一點就會TLE。
當然,跟染色問題一同存在的還有就是離散化,很多博客也是將這兩者一起來寫的,並且主要是離散化。但我個人感覺染色問題對理解線段樹很有幫助(當然還是太菜了= =大佬們都認爲難點是線段樹的離散化),所以就單獨拿出來了。
建樹
線段樹有多種表現形式,感覺很多人都是拿結構體寫的,博主是用數組寫的,其本質還是一樣的代碼有些不一樣而已。
整體上,我們對一段數據建成線段樹,那麼區域染色的時候就類似與修改區間。而線段樹的精髓就是利用lazy數組,可以保證不遍歷到子結點就可以獲得區域的情況。那麼染色也要利用這個性質,對於一段區間,我們想要在上層結點上來表示出來。那麼我們可以設置3個變量:-1 表示當前區段有多種顏色,具體有多少種不用管。0表示當前區域未染色。正整數表示當前區域染了單一染色,並且顏色號是這個正整數。
所以除了特殊要求,一般我們用int來表示顏色種類(就算題目是char類型或者string,我們也可以變成int,最多寫一個hash,還是整數方便)。那麼看下面這個例子:
這是按照上面的要求寫的線段樹,-1表示當前段有多種顏色,1表示當前段全部是1。這裏左邊,一個1一個0,而上面結點仍然是-1。這是因爲默認把0也當成一種顏色了,可以節省代碼,實際在查找的時候我們只用排除0就可以得到正確答案。
總之,這樣建立的線段樹可以表示區域染色的大致情況。而具體有多少種顏色需要在查詢的時候進行操作。
關於建樹,因爲染色問題,所以有覆蓋這種說法,後塗的顏色會覆蓋先塗的,所以一般原數據是什麼都沒有,也就是整個線段樹的值全是0。這樣初始化代碼只用將線段樹初始化就行了。即:
void build(){
memset(tree,0,sizeof(tree));
}
// 有這樣的情況,底層有規定顏色,那麼就不能用memset而是要用for循環
void build(int color){
for(int i = 1;i <= len;i++){
tree[i] = color;
}
}
不排除有剛開始就有塗色的情況,不過很簡單,在基礎的線段樹上添加上面的規定就行,這裏就不放代碼了。
區間染色
結點表示的含義如上,那麼區間染色基本上就是基於正常的線段樹進行改造了。先上代碼:
void update_range(int node, int l, int r, int L, int R, int add) {
if (l <= L && r >= R) {
lz[node] = add;
tree[node] = add; // 更新方式
return;
}
push_down(node, L, R);
int mid = (L + R) / 2;
if (mid >= l) update_range(node * 2, l, r, L, mid, add);
if (mid < r) update_range(node * 2 + 1, l, r, mid + 1, R, add);
if (tree[node * 2] == tree[node * 2 + 1]) {
tree[node] = tree[node * 2];
}else {
tree[node] = -1;
}
}
仍然需要利用二分來找到具體的區間,但是重點在於更新方式:進入剛開始的if語句,說明此時到達的要麼是整段的區間要麼是葉子結點。所以直接更新爲當前染色就行了,不要管下面還有多少顏色,反正全部被覆蓋了,就剩這一種。
而下面遞歸之後的回溯需要稍微考慮一下。有5種情況:
1.區間內有隻有一種顏色,即左右子樹顏色值一樣。那麼父結點當然也要跟子結點值一樣。
2.區間內什麼顏色也沒有,全部爲0。
3.區間內顏色很雜,左右子樹全部爲-1。
4.區間內顏色很雜,但是左右子樹是純色,即值爲正整數卻不相同。
5.區間內顏色很雜,左右子樹一個純色一個雜色,即值一個爲-1,一個爲正整數。
這4種情況其實1、2、3一樣,4、5一樣。只要左右子樹值一樣,父結點直接賦值,0也一樣正確,-1也一樣,反正子區間很雜,那父區間肯定更雜了-1就行。4、5都是區間雜亂,直接-1就行。
這裏可能會造成這樣的困擾,例如左子樹是純色2,右子樹無顏色爲0。那麼父結點應該爲2啊,上面卻寫成-1了。其實沒必要分這麼細,因爲是-1,所以查找的時候仍然向下查,左子樹爲2,記一種顏色,右子樹爲0,判定爲0不加顏色。這樣就解決了。節省了代碼。
push函數的改動
既然是染色,但是我們仍然需要lazy數組,標記下面的區域全部爲某種純色,也是完全可以使用的。那麼push函數就需要一定的改變,非常簡單的改動。改成現在的模式就可以了:
void push_down(int node, int l, int r) {
if (lz[node]) {
int mid = (l + r) / 2;
lz[node * 2] = lz[node];
lz[node * 2 + 1] = lz[node];
tree[node * 2] = lz[node];
tree[node * 2 + 1] = lz[node];
lz[node] = 0;
}
}
仍然是向下傳遞,只是直接賦值就行,不用管區間和。
區間查詢染色數量
仍然利用上面的規則,仍然是二分向下查詢。全局定義一個ans變量記錄數量,當碰見0,說明下面沒有顏色了,直接return。碰見正整數,說明下面全是單色,ans++同時return。當碰見-1,說明下面還有多種顏色,二分繼續向下查找。這樣,就也是在原來線段樹區間查詢的基礎上進行修改了:
void query_range(int node, int L, int R, int l, int r) {
if (l <= L && r >= R) {
if (tree[node] == 0) return;
if (tree[node] == -1) {
push_down(node, L, R);
int mid = (L + R) / 2;
if (mid >= l) query_range(node * 2, L, mid, l, r);
if (mid < r) query_range(node * 2 + 1, mid + 1, R, l, r);
}else {
if (color[tree[node]] == 0) {
ans++;
color[tree[node]] = 1;
}
}
return;
}
push_down(node, L, R);
int mid = (L + R) / 2;
if (mid >= l) query_range(node * 2, L, mid, l, r);
if (mid < r) query_range(node * 2 + 1, mid + 1, R, l, r);
}
總結
當然大佬們對染色問題不過多解釋是有原因的,因爲本質上仍然是對線段樹的擴展應用。不算一種單純的模板。最早是poj的2528題,因爲既有染色問題又有離散化的問題,而在找題解的時候發現都跟離散化有關,而染色問題卻沒多少人提。最早的想法是結點存區間總共有幾種顏色,那麼要求全區間的話只用看根結點就可以了。但這樣就對線段樹的操作太深了,反而影響了速度。後來發現有人用0、-1、正整數來作爲結點標誌,纔想到這個方法。
總之,線段樹的區間求和太模板了,出題當然不會這麼裸。肯定要變形,重點在於如何根據題意在線段樹的原理上對原始的線段樹進行改造,就像染色問題一樣。基本上在原線段樹的基礎上修改。
模板題推薦
emm,csdn超鏈接好像出毛病了,之後能改了再說吧= = poj-2777 Count Color
這個題因爲顏色數量不多,有大佬認爲可以用二進制來存某節點下總共有哪幾種顏色,個人認爲有點太蛋疼了,除非要求確切輸出有哪幾種顏色,這樣的算法在時間上沒太大優勢。
題解就跟上面一樣,因爲整體規模不大,所以不需要線段樹的離散化。AC代碼如下:
#include<iostream>
#include<vector>
#include<list>
#include<string>
#include<cmath>
#include<algorithm>
#include<map>
#include<set>
#include<utility>
#include<queue>
#include<sstream>
#include<iterator>
#include<math.h>
#include<malloc.h>
#include<string.h>
#define TIME std::ios::sync_with_stdio(false)
#define LL long long
#define MAX 101000
#define INF 0x3f3f3f3f
using namespace std;
int tree[MAX * 4]; // 線段樹
int lz[MAX * 4]; // 延遲標記
int N, colors, M;
int color[35];
int ans;
void init() {
for (int i = 0; i <= MAX * 4; i++) {
tree[i] = 1;
}
memset(lz, 0, sizeof(lz));
memset(color, 0, sizeof(color));
}
void push_down(int node, int l, int r) {
if (lz[node]) {
int mid = (l + r) / 2;
lz[node * 2] = lz[node];
lz[node * 2 + 1] = lz[node];
tree[node * 2] = lz[node];
tree[node * 2 + 1] = lz[node];
lz[node] = 0;
}
}
// 區間更新,lr爲更新範圍,LR爲線段樹範圍,add爲更新值
void update_range(int node, int l, int r, int L, int R, int add) {
if (l <= L && r >= R) {
lz[node] = add;
tree[node] = add; // 更新方式
return;
}
push_down(node, L, R);
int mid = (L + R) / 2;
if (mid >= l) update_range(node * 2, l, r, L, mid, add);
if (mid < r) update_range(node * 2 + 1, l, r, mid + 1, R, add);
if (tree[node * 2] == tree[node * 2 + 1]) {
tree[node] = tree[node * 2];
}else {
tree[node] = -1;
}
}
void query_range(int node, int L, int R, int l, int r) {
if (l <= L && r >= R) {
if (tree[node] == 0) return;
if (tree[node] == -1) {
push_down(node, L, R);
int mid = (L + R) / 2;
if (mid >= l) query_range(node * 2, L, mid, l, r);
if (mid < r) query_range(node * 2 + 1, mid + 1, R, l, r);
}else {
if (color[tree[node]] == 0) {
ans++;
color[tree[node]] = 1;
}
}
return;
}
push_down(node, L, R);
int mid = (L + R) / 2;
if (mid >= l) query_range(node * 2, L, mid, l, r);
if (mid < r) query_range(node * 2 + 1, mid + 1, R, l, r);
}
int main() {
TIME;
while (scanf("%d%d%d",&N,&colors,&M) != EOF) {
getchar();
init();
int l, r, colo;
char c;
while (M--) {
scanf("%c",&c);
if (c == 'C') {
scanf("%d%d%d",&l,&r,&colo);
if (r < l) {
int temp = r;
r = l;
l = temp;
}
update_range(1, l, r, 1, N, colo);
}else {
scanf("%d%d",&l,&r);
if (r < l) {
int temp = r;
r = l;
l = temp;
}
memset(color, 0, sizeof(color));
ans = 0;
query_range(1, 1, N, l, r);
cout << ans << endl;
}
getchar();
}
}
system("pause");
return 0;
}