POJ 2528 线段树+离散化(水水的线段树+略复杂的离散化)

题意:有一条数轴[1,1QW],给定m(最大1W)个区间,相当于给每个区间染色,每一次都是使用的不同的颜色,问最后能在整个数轴上看到多少颜色。
INPUT:第一行数据组数,每组数据第一行m表示有m个区间,之后的m行每行两个数,表示区间。

大家都说这是一道线段树的入门题,水题,却卡了我很长时间。
我一开始还在思考,这题要在线段树节点中附加多少信息啊,一开始思考各种天花乱坠,代码也因为附加信息的增加越写越长,因为我一直想的是最后输出的答案就是线段树第一个节点,所以搞得附加信息越来越多。
不过根本不用那么麻烦,这道题之所以说是线段树的入门题,是因为它甚至连一个附加信息都没有,只需要一个lazy-tag,所以说是入门题,代码的线段树部分也是非常的好写。不过理解起来有点麻烦,我就是在理解这颗线段树时上碰到了难处。

这道题利用的只是线段树的区间修改,所以有一个pushdown函数。
为了表述方便,暂且说lazy-tag是一个需要在pushdown过程中重置为0的,附加信息是lazy-tag的对象,不需要在pushdown后重置为0。因为这道题与线段树有关的数组只有一个,所以我纠结它到底是lazy-tag还是附加信息,当发现吧pushdown中的col[p] = 0去掉后输出就不对了,思考许久,是这样的,因为col[p]表示的是p节点代表的区间是col[p]这个颜色,在我们查询时,是直接查询到底的,而且查询的过程中还要不断pushdown,如果在之前的pushdown中不把col[p]置为0,那么它会产生影响,可以理解为后效性。这一整段是在说pushdown函数中简简单单的一句col[p] = 0,存在的意义的,可能你们都懂,跳过这一段那就好了,我在这个的理解上花了一段时间。

然而这道题还需要离散化,普通的离散化是不对的,看了众神犇的做法:要往两个间距大于1的点中再插入一个点,不过这个点在从排序后的数组转化为离散后的区间时是没用的。如何证明普通的离散是不对的,我也只能给出一个例子,如三张海报为:1~10 1~4 6~10,普通离散化之后为1~4 1~2 3~4,就出错了。

总结地来说一遍:
首先离散化,需要注意细节,否则离散化的结果很容易出错,要区分开这个点是左端点还是右端点,我们可以用负数表示其中的一个;
之后把每个区间添加到线段树(注意离散化的时候不要打乱区间的顺序),只需要传递一个lazy-tag,表示当前区间是哪个海报即可。查询时要查询到线段树的叶子节点,就是遍历所有节点,把出现过的海报记录下来,输出个数即可。
就我理解,这样解的本质还是维护这个数轴,每次都是把一个区间的所有点改成一个数,最后遍历这个数轴,数出有多少不同的数。这个数的过程和我们的实际做法差不多,但是这个本质当然会T,所以我们就用了线段树优化了区间修改这一步,所以也说,这颗线段树只需要一个lazy-tag。
线段树也是一个很灵活的东西。

代码:

#include <cstdio>
#include <cstring>
#include <algorithm>
#define M 22222
using namespace std;

int N, a, b, k, ans;
int l[M], r[M], jud[M];
int col[M<<4];

struct node{
    int num, key;
}t[M<<3];
bool cmp(node a, node b){ return a.key < b.key;}

void pushdown(int p){
    if(col[p]){
        int lc = p << 1, rc = p << 1 | 1;
        col[lc] = col[rc] = col[p];
    }
    col[p] = 0;
}

void update(int p, int l, int r){
    if(l >= a && r <= b){col[p] = k; return;}
    int lc = p << 1, rc = p << 1 | 1, mid = (l+r) >> 1;
    pushdown(p);
    if(a <= mid) update(lc, l, mid);
    if(b > mid) update(rc, mid+1, r);
}

void query(int p, int l, int r){
    if(col[p]){
        if(!jud[col[p]]) ans++;
        jud[col[p]] = 1;
        return;
    }
    pushdown(p);
    query(p<<1, l, (l+r)>>1);
    query(p<<1|1, (l+r)>>1|1, r);
} 

int main()
{
    scanf("%d", &N);
    while(N--){
        memset(col, 0, sizeof col);
        memset(jud, 0, sizeof jud);
        memset(t, 0, sizeof t);
        int m, cnt = 0, tot = 1;
        ans = 0; 
        scanf("%d", &m);
        for(int i = 1; i <= m; i++){
            scanf("%d %d", l+i, r+i);
            t[++cnt].num = -i; 
            t[cnt].key = l[i];
            t[++cnt].num = i;
            t[cnt].key = r[i];
        }
        sort(t+1, t+cnt+1, cmp);
        for(int i = cnt; i > 1; i--)
            if(t[i].key - t[i-1].key > 1) t[++cnt].key = t[i].key - 1;
        sort(t+1, t+cnt+1, cmp);
        l[-t[1].num] = 1;
        for(int i = 2; i <= cnt; i++){
            if(t[i].key != t[i-1].key) tot++;
            if(t[i].num > 0)  r[t[i].num] = tot;
            else l[-t[i].num] = tot;
        }
        for(int i = 1; i <= m; i++){
            a = l[i], b = r[i], k = i;
            update(1, 1, tot);
        }
        query(1, 1, tot);
        printf("%d\n", ans);
    }
} 

做了这道题,真是深深地加深了我对离散化的理解!!!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值