POJ - 2528 Mayor's posters(线段树+离散化)

题目链接

POJ - 2528

题目大意

有一面墙(墙最长 1e7 ), n 张海报,每张海报都和墙等高。每张海报都有一个liri,表示它会被贴在区间 [li,ri] 上,问所有海报都贴完之后,还能看见几张海报?(多组)

数据范围

1n100001liri1e7

解题思路

首先,对于一个区间 [li,ri] ,将这个区间整个染成某种颜色(若某个区间多次染色,则之后染的色覆盖之前的,以此贴合海报的相互覆盖),最后去统计整个大区间所含有的颜色,就是问题的答案。
然而给的数据达到了 1e7 ,不光空间开不下,时间也够呛!这就需要离散化一下:


插播一下离散化:
我理解的离散化就是:在不改变数据的相互关系的前提下,将大数据映射到小数据上,以此节省空间。
举个例子: 3线[1,100],[200,300],[1,250]
取每个线段端点,排序去重后:

1,100,200,250,300

1,2,3,4,5

3 线段就变为 [1,2],[3,5],[1,4] ,这样不但没有改变线段之间的相互关系,而且还将空间从 3005 ,是不是很赚!?


回到这道题,最多有 1e4 个区间,离散化后最多就 2e4 个点, 1e7 -> 2e4 ,收益还是很可观的。对离散化后的数组进行建树,成段更新(lazy思想),最后统计有多少种不同颜色就好了。



外话

网上有人说这道题普通离散化有问题,

在我理解看来就是将一个很大的区间映射为一个很小的区间,而不改变原有的大小覆盖关系,但是注意简单的离散化可能

会出现错误,给出下面两个简单的例子应该能体现普通离散化的缺陷:
例子一:1-10 1-4 5-10
例子二:1-10 1-4 6-10
普通离散化后都变成了[1,4][1,2][3,4]
线段2覆盖了[1,2],线段3覆盖了[3,4],那么线段1是否被完全覆盖掉了呢?
例子一是完全被覆盖掉了,而例子二没有被覆盖

解决的办法则是对于距离大于1的两相邻点,中间再插入一个点,本题还用到了Lazy标记的思想

而我觉得并没有什么问题。我分析一下这个问题的原因:他们应该是采用了区间标号(自创名词) 才会出现这样的问题。
我所说的区间标号是指:区间[1, 2]标号为1,[2, 3]标号为2 以此类推。就这个而言:1-10 1-4 5-10,4号区间和5号区间是紧挨着的,所以才会出现线段1被完全覆盖的情况,以致于出现错误答案2。
然而,用端点表示区间的方法完全不存在这个问题,还是上面那个例子,区间[4, 5]是没有被覆盖的,完全能看到正确的 3种颜色啊!
从题面上看,我并没有看出出题人说采用的哪种方式,那估计就是端点法咯!这也是能A的。
通过那个问题,学到了一个小技巧,就是在长度大于1的区间中再加一个 中间点 ,这样不管哪种都是能对的。





代码采用端点表示区间的方法:

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <set>
#include <map>
#include <queue>
using namespace std;
typedef long long LL;
const int inf = 1 << 30;
const LL INF = 1LL << 60;
const int MaxN = 1e4;

int T;
int n, tot, m;
int ans;
int le[MaxN + 5], ri[MaxN + 5];
int x[4 * MaxN + 5];
bool vis[16 * MaxN + 5];
struct segtree
{
    int l, r;
}tree[16 * MaxN + 5];
int col[16 * MaxN + 5];
//节点的颜色,相当于lazy数组,若col[i] == -1则没有染色

void Build(int rt, int l, int r)
{
    tree[rt].l = l, tree[rt].r = r;
    if(l == r) return;
    int mid = (l + r) >> 1;
    Build(rt << 1, l, mid);
    Build(rt << 1 | 1, mid + 1, r);
}

void push_down(int rt)
{
    if(col[rt] != -1) {
        col[rt << 1] = col[rt];
        col[rt << 1 | 1] = col[rt];
        col[rt] = -1;
    }
}

void update(int rt, int L, int R, int c)
{
    //若所要染色的区间完全包含 当前节点所管辖的区间
    //直接将这个节点染色,不必再往下染色了
    if(L <= tree[rt].l && tree[rt].r <= R) {
        col[rt] = c;
        return ;
    }
    //否则下放标记,即对左右儿子染色
    push_down(rt);
    int mid = (tree[rt].l + tree[rt].r) >> 1;
    if(L <= mid) update(rt << 1, L, R, c);
    if(R > mid) update(rt << 1 | 1, L, R, c);
}

void query(int rt)
{
    if(col[rt] != -1) { //说明这个节点所管辖的区间是纯色
        if(vis[col[rt]] == false) {
            ans++;
            vis[col[rt]] = true;
        }
        return;
    }
    push_down(rt); //这个不用都能过,想想why?
    if(tree[rt].l == tree[rt].r) return;
    query(rt << 1);
    query(rt << 1 | 1);
}

//查找数组中第一个小于等于y的值的位置,相当于lower_bound
int bin_search(int y)
{
    int l = 1, r = m;
    int mid = 0, res = 0;
    while(l <= r) {
        mid = (l + r) >> 1;
        if(x[mid] >= y) res = mid, r = mid - 1;
        else l = mid + 1;
    }
    return res;
}

int main()
{
    scanf("%d", &T);
    while(T--) {
        ans = 0; tot = 0;
        memset(col, -1, sizeof(col));
        memset(vis, 0, sizeof(vis));

        scanf("%d", &n);
        for(int i = 1; i <= n; i++) {
            scanf("%d %d", &le[i], &ri[i]);
            x[++tot] = le[i];
            x[++tot] = ri[i];
        }
        //--------离散化------
        sort(x + 1, x + tot + 1);
        m = 1;
        for(int i = 2; i <= tot; i++) {
            if(x[i] != x[i - 1]) //去重
                x[++m] = x[i];
        }
        sort(x + 1, x + m + 1);

        Build(1, 1, m); //对离散后的数组进行建树
        for(int i = 1; i <= n; i++) {
            //二分查找到离散后的新区间
            int newl = bin_search(le[i]);
            int newr = bin_search(ri[i]);
            //将新区间染色i这种颜色
            update(1, newl, newr, i);
        }
        ans = 0;
        query(1);
        printf("%d\n", ans);
        memset(tree, 0, sizeof(tree));
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值