..

训练记录

$\color{#52C41A} \text{P3740 [HAOI2014]贴海报}$

区间覆盖问题。

法一|离散化 线段树

  • 值域很大 $0 \le N \le 10000000$
  • 数量很少 $1 \le M \le 1000$

考虑离散化。

由于区间夹缝不好处理,将左右端点附近的数加入值域。

void Discrete() {
    for (int i = 1; i <= M; i++) {
        range.push_back(A[i] - 1);
        range.push_back(A[i]);
        range.push_back(A[i] + 1);
        range.push_back(B[i] - 1);
        range.push_back(B[i]);
        range.push_back(B[i] + 1);
    }
    std::sort(range.begin(), range.end());
    range.erase(std::unique(range.begin(), range.end()), range.end());
    for (int i = 1; i <= M; i++) {
        A[i] = std::lower_bound(range.begin(), range.end(), A[i]) - range.begin();
        B[i] = std::lower_bound(range.begin(), range.end(), B[i]) - range.begin();
    }
    return;
}

同时,线段树的值域也要根据离散化值域放缩。

    SGT.Build(SGT.root, 1, M * 5);

从最新贴上的海报开始处理。

用线段树修改查询 $[A_i, B_i]$ 内是否有未覆盖的子区间,若存在打上标记并计数。

    for (int i = M; i >= 1; i--) {
        appeared = false;
        SGT.Modify(SGT.root, A[i], B[i]);
        if (appeared) ans++;
    }
    void Modify(int xNode, int l, int r) {
        if (node[xNode].vis) return;

    }
    void PushUp(int xNode) {
        node[xNode].vis = node[node[xNode].lNode].vis && node[node[xNode].rNode].vis;
        return;
    }

法二|浮水法

后面写专栏讲。

法三|暴力

为什么暴力模拟能过?

双倍经验

$\color{#3594D3} \text{UVA1608 Non-boring sequences}$

  • 值域很大 $\text{The elements arenon-negative integers less than} 10^9.$
  • 数量很少 $1 \le n \le 200000$

考虑离散化。

从元素角度考虑。

  • 找到上一个与 $a_i$ 相同的元素下标作为前驱,记为 $last_i$,若未找到则为 $0$;
  • 找到下一个与 $a_i$ 相同的元素下标作为后继,记为 $next_i$,若未找到则为 $n + 1$。

满足下述条件的区间 $[l, r]$一定为 $\text{Non-boring sequences}$:

  • $l \in (last_i, i]$;
  • $r \in [i, next_i]$。

因为要求 $a_i$ 在 $[l, r]$ 中出现且仅出现一次。

转化为区间覆盖问题。

  • 以 $l$ 为横轴;
  • 以 $r$ 为纵轴。

转化为扫描线面积并问题。

__stdcall 称之为 $\text{unique}$ 模型。

很恶心。

调了一两个小时。

数据范围

根据讨论区,要开 long long

否则 $\text{WA}$。

多测清零

各种用到的数据都要清零。

    for (long long i = 1; i <= n; i++) appear[i].clear();
    event.clear();
    SGT.Clear();

清零时可以适当缩减范围。

    void Clear() {
        cnt = 0;
        root = 0;
        for (long long i = 0; i <= (maxN * 4); i++) {
            node[i].l = 0;
            node[i].r = 0;
            node[i].lNode = 0;
            node[i].rNode = 0;
            node[i].tag = 0;
            node[i].sum = 0;
        }
        return;
    }

每一组数据都以 $maxN$ 为界限会 $\text{TLE}$。

    void Clear() {
        cnt = 0;
        root = 0;
        for (long long i = 0; i <= (n * 4); i++) {
            node[i].l = 0;
            node[i].r = 0;
            node[i].lNode = 0;
            node[i].rNode = 0;
            node[i].tag = 0;
            node[i].sum = 0;
        }
        return;
    }

结构体排序

struct Event {
    long long x;
    long long y1, y2;
    long long delta;

    bool operator<(const Event& other) const {
        if (x != other.x) return x < other.x;
        return delta < other.delta;
    }
};

其实这道题并没用到那么多条件。

struct Event {
    long long x;
    long long y1, y2;
    long long delta;

    bool operator<(const Event& other) const {
        return x < other.x;
    }
};

但是存在一些题需要根据 $delta$ 增减性确定顺序。

具体是啥暂时还想不到例子。

$\text{STL}$ 容器边界

    for (long long i = 1; i <= n; i++) {
        for (long long j = 0; j < appear[i].size(); j++) {
            if (appear[i].size() == 1) {
                last[appear[i][j]] = 0;
                next[appear[i][j]] = n + 1;
                continue;
            }
            if (j == 0) {
                last[appear[i][j]] = 0;
                next[appear[i][j]] = appear[i][j + 1];
                continue;
            }
            if (j == appear[i].size() - 1) {
                last[appear[i][j]] = appear[i][j - 1];
                next[appear[i][j]] = n + 1;
                continue;
            }
            last[appear[i][j]] = appear[i][j - 1];
            next[appear[i][j]] = appear[i][j + 1];
        }
    }

$\text{STL}$ 容器越界访问会 $\text{RE}$。

所以要分情况讨论。

推荐用手写基础数据结构。

但是我不喜欢。