题解:AtCoder AT_awc0015_e Count the Types of Flowers

【题目来源】

AtCoder:E - Count the Types of Flowers

【题目描述】

Takahashi works as a caretaker at a botanical garden. The garden has \(N\) flower beds arranged in a straight line, numbered \(1, 2, \ldots, N\) from left to right. Each flower bed \(i\) \((1 \leq i \leq N)\) contains a flower of species number \(P_i\). Species numbers are integers between \(1\) and \(N\) inclusive, and different flower beds may contain flowers of the same species number.
高桥在一座植物园担任管理员。花园里有 \(N\) 个花坛排成一条直线,从左到右编号为 \(1, 2, …, N\)。每个花坛 \(i\)\(1 ≤ i ≤ N\))包含一种编号为 \(P_i\) 的花卉。物种编号是介于 \(1\)\(N\) 之间的整数,不同的花坛可能包含相同物种编号的花卉。

The botanical garden has \(Q\) tour visits scheduled. The \(i\)-th tour \((1 \leq i \leq Q)\) visits the range from flower bed \(L_i\) to flower bed \(R_i\) (inclusive).
植物园安排了 \(Q\) 次参观游览。第 \(i\) 次参观(\(1 ≤ i ≤ Q\))将访问从花坛 \(L_i\) 到花坛 \(R_i\)(包含两端)的范围。

Takahashi wants to know in advance how many distinct types of flowers are in each tour's visiting range, in order to prepare the guide for each tour.
高桥希望提前了解每次参观的访问范围内有多少种不同的花卉类型,以便为每次参观准备导览。

For each tour, determine the number of distinct species numbers contained in the range from flower bed \(L_i\) to flower bed \(R_i\).
对于每次参观,确定从花坛 \(L_i\) 到花坛 \(R_i\) 的范围内包含的不同物种编号的数量。

【输入】

\(N\) \(Q\)
\(P_1\) \(P_2\) \(\ldots\) \(P_N\)
\(L_1\) \(R_1\)
\(L_2\) \(R_2\)
\(\vdots\)
\(L_Q\) \(R_Q\)

  • The first line contains an integer \(N\) representing the number of flower beds and an integer \(Q\) representing the number of tours, separated by a space.
  • The second line contains integers \(P_1, P_2, \ldots, P_N\) representing the species numbers of the flowers planted in each flower bed, separated by spaces.
  • The following \(Q\) lines give the visiting range for each tour.
  • The \(i\)-th of these lines \((1 \leq i \leq Q)\) contains the left endpoint \(L_i\) and right endpoint \(R_i\) of the visiting range for the \(i\)-th tour, separated by a space.

【输出】

Output \(Q\) lines. The \(i\)-th line \((1 \leq i \leq Q)\) should contain the number of distinct species numbers in the range from flower bed \(L_i\) to flower bed \(R_i\) for the \(i\)-th tour.

【输入样例】

5 3
1 2 1 3 2
1 3
2 5
1 5

【输出样例】

2
3
3

【核心思想】

  1. 问题分析:给定 \(N\) 个花坛,每个花坛有一种花卉物种 \(P_i\)。有 \(Q\) 次查询,每次查询区间 \([L_i, R_i]\) 内不同物种的数量。这是一个树状数组 + 离线查询问题,关键在于高效处理区间不同元素计数。

  2. 算法选择

    • 离线处理(Offline Processing):将查询按右端点 \(R_i\) 升序排序,从左到右扫描数组,动态维护每个物种的最后出现位置
    • 树状数组(Fenwick Tree / BIT):维护当前已扫描位置的"有效标记",每个物种只在其最后出现的位置标记为 \(1\),其他位置标记为 \(0\)
    • last 数组:记录每个物种上次出现的位置,用于取消旧标记
  3. 关键步骤

    • 初始化
      • 读取 \(N\)(花坛数量)、\(Q\)(查询次数)
      • 读取物种数组 \(P[1..N]\)
      • 读取 \(Q\) 个查询,存储为结构体数组(包含 \(id\), \(l\), \(r\)
      • 将查询按右端点 \(r\) 升序排序
      • 初始化树状数组 trlast 数组(记录每个物种最后出现位置,初始为 \(0\)
    • 扫描处理(按排序后的查询顺序):
      • 维护当前扫描位置 cur(从 \(1\) 开始)
      • 对于每个查询 \((l, r, id)\)
        • 扩展扫描范围while (cur <= r),处理数组位置直到达到当前查询的右端点 \(r\)
          • 获取当前位置的物种 species = P[cur]
          • 若该物种之前出现过(last[species] != 0):在 last[species] 位置执行 add(last[species], -1) 取消旧标记
          • 在当前位置 cur 执行 add(cur, 1) 添加新标记
          • 更新 last[species] = cur
          • cur++
        • 回答查询ans[id] = query(r) - query(l - 1),计算区间 \([l, r]\) 内的不同物种数量(即有效标记之和)
    • 输出答案:按原始查询顺序输出 ans[1..Q]
  4. 时间/空间复杂度

    • 时间复杂度:\(O((N + Q) \log N)\),排序 \(O(Q \log Q)\),每个位置最多被添加和删除各一次,每次操作 \(O(\log N)\)
    • 空间复杂度:\(O(N + Q)\),树状数组、last 数组、查询数组、答案数组
  5. 树状数组 + 离线查询的核心思想

    • 离线处理转化动态问题:通过改变查询顺序(按右端点排序),将动态区间查询转化为静态扫描问题。从左到右扫描时,只需考虑当前已扫描范围内的信息
    • 每个物种只保留最后出现位置:树状数组维护的是"当前扫描范围内,每个物种最右出现位置"的标记。这样查询区间 \([l, r]\) 时,若某物种在区间内有多个出现,只有最右的那个会被计入(因为其他位置的标记已被取消)
    • 取消旧标记机制:当物种 \(x\) 在位置 \(i\) 再次出现时,在 last[x] 位置减 \(1\)(取消旧标记),在位置 \(i\)\(1\)(添加新标记)。这样树状数组始终只记录每个物种在当前扫描范围内的最后出现位置
    • 前缀和查询区间:树状数组的前缀和 query(r) - query(l-1) 正好统计区间 \([l, r]\) 内有多少个不同的"最后出现位置",即不同物种数量
    • 适用于区间不同元素计数、离线查询优化类问题

【解题思路】

【算法标签】

树状数组

【代码详解】

#include <bits/stdc++.h>
using namespace std;
const int N = 100005;
int n, q;  // n: 数组长度,q: 查询次数
int p[N], last[N];  // p: 原数组,last: 记录每个物种上次出现的位置
int tr[N], ans[N];  // tr: 树状数组,ans: 存储查询结果
struct Node
{
    int id, l, r;  // 查询的编号、左边界、右边界
}Q[N];

// 计算lowbit:返回x的二进制表示中最低位的1所对应的值
int lowbit(int x)
{
    return x & -x;
}

// 树状数组更新操作:在位置x增加c
void add(int x, int c)
{
    for (int i = x; i <= n; i += lowbit(i))
    {
        tr[i] += c;
    }
}

// 树状数组查询操作:查询前缀和[1, x]
int query(int x)
{
    int res = 0;
    for (int i = x; i; i -= lowbit(i))
    {
        res += tr[i];
    }
    return res;
}

// 查询按右端点排序的比较函数
bool cmp(Node x, Node y)
{
    return x.r < y.r;  // 按右端点升序排序
}

int main()
{
    cin >> n >> q;  // 读入数组长度和查询次数
    for (int i = 1; i <= n; i++)
    {
        cin >> p[i];  // 读入原数组
    }
    for (int i = 1; i <= q; i++)  // 读入查询
    {
        int l, r;
        cin >> l >> r;
        Q[i] = {i, l, r};  // 存储查询编号、左右边界
    }

    sort(Q + 1, Q + q + 1, cmp);  // 将查询按右端点排序

    int cur = 1;  // 当前处理的数组位置
    for (int i = 1; i <= q; i++)  // 处理每个查询
    {
        int l = Q[i].l, r = Q[i].r, id = Q[i].id;

        // 处理数组位置,直到达到当前查询的右端点
        while (cur <= r)
        {
            int species = p[cur];  // 当前位置的物种
            if (last[species] != 0)  // 如果这个物种之前出现过
            {
                add(last[species], -1);  // 在之前出现的位置-1
            }
            add(cur, 1);  // 在当前位置+1
            last[species] = cur;  // 更新这个物种最后出现的位置
            cur++;
        }

        // 查询区间[l, r]的不同物种数量
        ans[id] = query(r) - query(l - 1);
    }

    for (int i = 1; i <= q; i++)
    {
        cout << ans[i] << endl;  // 输出所有查询结果
    }
    return 0;
}

【运行结果】

5 3
1 2 1 3 2
1 3
2
2 5
3
1 5
3
posted @ 2026-06-15 09:48  团爸讲算法  阅读(5)  评论(0)    收藏  举报