GDUT ACM2022寒假集训 专题五 G(线段树+离散化+染色)

一、例题(Mayor’s posters)

原题链接:https://vjudge.net/contest/479523#problem/G

1、题意

有一堵墙,往上面贴海报,每张海报有各自的范围,可以相互覆盖,求最后还能看见多少张海报?

2、输入格式

第一行一个整数n表示数据组数
每组数据第一行一个整数m表示海报数量
接下来m行每行两个整数l,r表示海报覆盖的范围
在这里插入图片描述

3、输出格式

一个整数表示最后还能看见的海报数量

4、样例

sample input1

1
5
1 4
2 6
8 10
3 4
7 10

sample output1

4

例题一题解

1、分析

(1)离散化

本题墙的长度数据过大,且题目答案与区间长度无关,只与数量有关,可以使用离散化将其区间缩小

离散化适用于,在整数集合z中涉及其中m个有限数值,且题目答案与所数值的绝对大小无关,只和他们的相对大小有关(或只和他们的相对顺序有关)

此时我们可以将z中的m个整数与1-m建立映射关系(一一对应)

此举可以大大降低时间复杂度和空间复杂度

c++具有stl函数unique来排除有序数组中的重复数字,该函数返回数组去重后,不重复部分最后一个数字的地址

(2)染色

对于本题,我们定义数字0-m代表m种颜色,保存在懒标记中,当某个节点存在懒标记,则表示当前情况该节点的所有子节点都被第x种颜色覆盖,即第x种颜色在该区间最上方

每次对懒标记进行修改或者对线段树进行查询时都要确认某点是否具有懒标记,具有懒标记则需要执行懒标记下传操作

(3)本题细节

本题中,直接进行离散化会导致情况遗漏

bTWcp8.jpg

如上图,我们先将所有数字存入num数组中排序并使用unique排除重复数字,得到最后一个数字下标为3

第一种情况,直接离散化,得到下图
bTWb1U.jpg

我们可以发现,原本后两张海报中间应当有一个空缺位,正确答案为3张海报,直接离散后空缺位消失,答案变为错误的2

因此我们需要在数值4和6之间插入一个数值4+1,也就是循环一次有效部分,每次找到相邻两数之差 n u m [ i ] − n u m [ i − 1 ] num[i]-num[i-1] num[i]num[i1]大于1时,在下标 ++len处添加一个值 n u m [ i − 1 ] + 1 num[i - 1] + 1 num[i1]+1

最后再 ++len处插入一个0,便于将num的下标0占位,对0-len的num数组范围进行排序,最后得到的num数组的1 - len就是最终的离散化数组

如下图
bThkGV.jpg

bThMI1.jpg

2、代码

#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstring>
using namespace std;
const int maxn = 2e5 + 5;
int lx[maxn * 10], rx[maxn * 10], num[maxn * 10];
int vis[maxn * 10];

struct tt
{
    int l, r, la;
} tree[maxn * 10];

void lazy(int k)//下传懒标记
{
    tree[k << 1].la = tree[k << 1 | 1].la = tree[k].la;
    tree[k].la = -1;
}

void build(int p, int l, int r)//建树
{
    tree[p].l = l;
    tree[p].r = r;
    tree[p].la = -1;
    if (l == r)
        return;
    int mid = l + r >> 1;
    build(p << 1, l, mid);
    build(p << 1 | 1, mid + 1, r);
}

void change(int k, int l, int r, int c) // c为地毯颜色
{
    if (tree[k].l == l && tree[k].r == r)
    {
        tree[k].la = c;
        return;
    }
    if (tree[k].la != -1)
    {
        lazy(k);
    }
    int mid = tree[k].l + tree[k].r >> 1;
    if (mid >= r)
    {
        change(k << 1, l, r, c);
    }
    else if (mid < l)
    {
        change(k << 1 | 1, l, r, c);
    }
    else
    {
        change(k << 1, l, mid, c);
        change(k << 1 | 1, mid + 1, r, c);
    }
}

int ans=0;
void query(int l, int r, int k)
{
    if (tree[k].la != -1)
    {
        if (!vis[tree[k].la])//标记颜色(海报)可见
        {
            ans++;
            vis[tree[k].la] = 1;
        }
        return;
    }
    if (l == r)
        return;
    int mid = tree[k].l + tree[k].r >> 1;
    query(l, mid, k << 1);
    query(mid + 1, r, k << 1 | 1);
}

int main()
{
    int n;
    cin >> n;
    while (n--)
    {
        int m, tot = 0, temp1=0; // tot左右区间所有的数字总数,temp1  tot去重之后的数字总数
        cin >> m;
        memset(vis, 0, sizeof(vis));
		memset(num, 0, sizeof(num));
        for (int i = 0; i < m; ++i)
        {
            cin >> lx[i] >> rx[i];
            num[tot++] = lx[i];
            num[tot++] = rx[i]; //存储所有左右区间的数字
        }
        sort(num, num + tot);
        temp1 = unique(num , num + tot) - num;//unique将num中重复数字移到数组末尾并返回有效部分地址
        int temp2 = temp1;
        for (int i = 1; i < temp2; ++i)//防止遗漏情况,两数之差大于1时中间插一个数
        {
            if (num[i] - num[i - 1] > 1)
            {
                num[temp1++] = num[i - 1] + 1;
            }
        }
        num[temp1++] = 0;//末尾插入一个0,便于sort时将0占位
        sort(num, num + temp1);
        build(1, 1, temp1);//建树
        int l, r, c;
        for (int i = 0; i < m; ++i)//上色(贴海报)
        {
            l = lower_bound(num, num + temp1, lx[i]) - num;
            r = lower_bound(num, num + temp1, rx[i]) - num;
            c = i;
            change(1, l, r, c);
        }
        ans = 0;
        query(1, temp1, 1);
        cout << ans << endl;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值