POJ

来源:互联网 发布:网络上gem是什么意思 编辑:程序博客网 时间:2024/06/14 17:46

题目链接

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_boundint 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;}
原创粉丝点击