线段树进阶-染色问题 附poj-2777题解

版权声明:转载请注明原址,有错误欢迎指出 https://blog.csdn.net/iwts_24/article/details/81603603

区域染色覆盖问题

        假设某大学有一面文化墙,各个学院都可以在上面涂色,要求涂色区域高必须和墙一样,宽度任意但必须是整数(以米为单位)。涂色可以覆盖其他学院的涂色。现在若干个学院涂色之后最终这面墙上能看见多少种颜色。

        这就是简单的染色问题了。当然有很多种不同的说法,但整体上离不开这两个问题: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;
}

猜你喜欢

转载自blog.csdn.net/iwts_24/article/details/81603603
今日推荐