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

题目链接
对于这个题,网上的很多题解对离散化的过程基本是一笔带过,本憨憨看了很久才懂。现在在这详细讲一下这个题区间的离散化。
首先,看下面这个例子(几个区间):

[1, 5]
[2, 7]
[8, 10]

在数轴上表示:
在这里插入图片描述
这是咱们发现一个事实:对于这些区间,我们保留某些点的信息是没有意义的,比如:3,4,6,9。对于这些点,它们一直被包含在一个大区间里。所以,我们考虑把这些点压缩点,你可以想象,把一个数组那些非端点给切掉,然后把剩下的部分拼接起来,然后把端点从1开始编号,就形成了:
在这里插入图片描述
不难发现,这个被压缩的数轴在这个题中与原数轴是等价的。这时,我们便完成了离散化的过程,(从10的长度压缩到了6)将一些没有必要记录信息的非端点压缩掉了。那么,我们应该怎么完成这个过程呢:
对于这个例子,我们先把三个区间读入,然后把所有端点存在一个数组中,排序,去重:

 1  2  5  7  8  9

这里注意的是:一个端可能在不同的区间中出现多次。而每个端点咱们存一次就好了。(判重)
那么,他在数组中的序号就是新端点的编号。
而对于这个题来将,传统离散有一个问题:
例如:

[1,10]
[1, 4]
[6,10]

答案应该是3,但是,如果按上述方法,答案会是2,原因:在压缩时,我们将仍然漏在外面的5给压缩掉了,所以我们在所有相隔距离大于1的点之间都插入一个单点(让它充当那段被裸露区间),然后此问题得以解决。
至此本题的离散化过程完成,下面的线段树就是模板了。阐述的题解有很多,这里一笔带过。

ps. 最后说一下两个stl函数吧,

unique(begin, end),对于一个容器、数组,自动判重(相邻位置),返回末地址。
lower_bound(begin,end,a)二分找非递减序列中第一个大于等于a的元素地址。

下面是ac代码(模板来自蓝皮书):

#include <iostream>
#include <string>
#include <cstring>
#include <queue>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#define ll long long
using namespace std;
const int N = 200005;
struct Node
{
    int l, r;
    int gg;
}tr[(N<<2)+5];
int lq[N], rq[N];
int lisan[N];
int cnt, ans;
bool vis[N];
void build(int p, int l, int r)
{
    tr[p].l = l; tr[p].r = r;
    if (l == r){tr[p].gg = 0; return;}
    int mid = (l + r) >> 1;
    build(p<<1, l, mid);
    build(p<<1|1, mid+1, r);
    tr[p].gg = 0;
}
void spread(int q)
{
    if (tr[q].gg == 0) return;
    tr[q<<1].gg = tr[q].gg;
    tr[q<<1|1].gg = tr[q].gg;
    tr[q].gg = 0;
}
void update(int q, int l, int r, int v)
{
    if (l <= tr[q].l && r >= tr[q].r)
    {tr[q].gg = v; return;}
    spread(q);
    int mid = (tr[q].l + tr[q].r) >> 1;
    if (l <= mid) update(q<<1, l, r, v);
    if (r  > mid) update(q<<1|1, l, r, v);
}
void ask(int q, int l, int r)
{
    if (tr[q].gg && !vis[tr[q].gg])
    {
        ans++;
        vis[tr[q].gg] = 1;
        return;
    }
    if (l == r) return;
    spread(q);
    int mid = (l + r) >> 1;
    ask(q<<1, l, mid);
    ask(q<<1|1, mid+1, r);
}
int main()
{
    int t;
    cin >> t;
    while(t--)
    {
        int n;
        cnt = 0;
        memset(vis, 0, sizeof(vis));
        scanf("%d", &n);
        for (int i = 0; i < n; i++)
        {
            scanf("%d%d", &lq[i], &rq[i]);
            lisan[cnt++] = lq[i];
            lisan[cnt++] = rq[i];
        }
        sort(lisan, lisan + cnt);
        int m = unique(lisan, lisan+cnt) - lisan;
        int t0 = m;
        for (int i = 1; i < m; i++)
            if(lisan[i] - lisan[i-1] > 1) lisan[t0++] = lisan[i-1] + 1;
        sort(lisan, lisan+t0);
        build(1, 1, t0);
        for (int i = 0; i < n; i++)
        {
            int x = lower_bound(lisan, lisan+t0, lq[i]) - lisan + 1;
            int y = lower_bound(lisan, lisan+t0, rq[i]) - lisan + 1;
          //  cout <<x << " " << y << endl;
            update(1, x, y, i + 1);
        }
        ans = 0;
        ask(1, 1, t0);
        printf("%d\n", ans);
    }
}

评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值