線段樹進階-染色問題 附poj-2777題解

區域染色覆蓋問題

        假設某大學有一面文化牆,各個學院都可以在上面塗色,要求塗色區域高必須和牆一樣,寬度任意但必須是整數(以米爲單位)。塗色可以覆蓋其他學院的塗色。現在若干個學院塗色之後最終這面牆上能看見多少種顏色。

        這就是簡單的染色問題了。當然有很多種不同的說法,但整體上離不開這兩個問題: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;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章