题解:洛谷 P3184 Counting Haybales
【题目来源】
洛谷:P3184 [USACO16DEC] Counting Haybales S - 洛谷
【题目描述】
Farmer John 刚刚将他的 \(N\) 个干草捆(\(1 \leq N \leq 100,000\))安排在他农场的一维道路上的各个位置。为了确保它们之间的间距合适,请帮助他回答 \(Q\) 个查询(\(1 \leq Q \leq 100,000\)),每个查询询问在道路的特定区间内有多少个干草捆。
【输入】
第一行包含 \(N\) 和 \(Q\)。
第二行包含 \(N\) 个不同的整数,每个整数的范围在 \(0 \ldots 1,000,000,000\) 之间,表示每个位置上有一个干草捆。
接下来的 \(Q\) 行每行包含两个整数 \(A\) 和 \(B\)(\(0 \leq A \leq B \leq 1,000,000,000\)),表示一个查询,询问在 \(A\) 到 \(B\)(包括 \(A\) 和 \(B\))的区间内有多少个干草捆。
【输出】
你需要输出 \(Q\) 行。对于每个查询,输出其对应区间内的干草捆数量。
【输入样例】
4 6
3 2 7 5
2 3
2 4
2 5
2 7
4 6
8 10
【输出样例】
2
2
3
4
1
0
【核心思想】
-
问题分析:给定 \(N\) 个干草捆的位置和 \(Q\) 个区间查询,求每个区间内的干草捆数量。坐标范围达 \(10^9\),但 \(N, Q \leq 10^5\)。本质上是离散化 + 树状数组问题:将大坐标范围压缩到有限空间,用树状数组高效统计区间和。
-
算法选择:
- 离散化:将所有出现过的坐标(干草捆位置和查询端点)排序去重,映射到 \(1 \sim M\) 的连续整数
- 树状数组(Fenwick Tree):支持 \(O(\log M)\) 的单点更新和区间查询
-
关键步骤:
- 读取数据:读入 \(N, Q\),\(N\) 个干草捆位置,\(Q\) 个查询区间
- 收集坐标:将所有干草捆位置和查询的左右端点加入
alls数组 - 离散化:
sort+unique去重,用lower_bound映射到 \(1 \sim M\) - 建树状数组:对每个干草捆位置
add(pos, 1) - 处理查询:对每个查询 \([l, r]\),输出
query(r) - query(l-1)
-
时间/空间复杂度:
- 时间复杂度:\(O((N+Q) \log (N+Q))\),离散化排序 + 树状数组操作
- 空间复杂度:\(O(N+Q)\),离散化数组和树状数组
-
离散化 + 树状数组的核心思想:
- 坐标压缩:将 \(10^9\) 范围的大坐标映射到 \(O(N+Q)\) 级别的小坐标,避免开巨大数组
- 树状数组的高效性:单点更新和前缀查询均为 \(O(\log M)\),区间查询通过两个前缀差实现
- 查询端点的必要性:查询区间 \([A, B]\) 的端点也需加入离散化,否则无法定位
- 离线处理:所有查询先读入再统一处理,适合批量查询场景
- 适用于大坐标范围、区间统计、离线查询类问题
【算法标签】
普及- #树状数组
【代码详解】
#include <bits/stdc++.h>
using namespace std;
const int N = 100005 * 3; // 最大数组大小(考虑离散化后的可能大小)
typedef pair<int, int> PII; // 定义pair类型,用于存储查询区间
int n, q; // n: 数组长度, q: 查询次数
int a[N]; // 原始数组
int tr[N]; // 树状数组,用于统计离散化后的值出现次数
vector<int> alls; // 离散化数组,存储所有需要离散化的值
vector<PII> queries; // 存储所有查询区间
/**
* 计算lowbit:获取x的最低位的1
* @param x 输入数值
* @return x的最低位的1所代表的值
*/
int lowbit(int x)
{
return x & -x; // 利用补码性质
}
/**
* 树状数组单点更新操作
* @param x 更新位置(离散化后的位置)
* @param c 增加的值
*/
void add(int x, int c)
{
// 树状数组标准更新操作,向上更新所有相关节点
// 注意:这里使用了固定的N作为上限,可能存在问题
for (int i = x; i <= N; i += lowbit(i))
tr[i] += c;
}
/**
* 树状数组前缀和查询操作
* @param x 查询位置(离散化后的位置)
* @return 前x个位置的和
*/
int query(int x)
{
int res = 0;
// 树状数组标准查询操作,向下累加所有相关节点
for (int i = x; i; i -= lowbit(i))
res += tr[i];
return res;
}
/**
* 离散化查找:在离散化数组alls中查找x的位置
* @param x 要查找的值
* @return x在离散化数组中的索引(从1开始)
*/
int find(int x)
{
// 使用二分查找在alls中找到x的位置
return lower_bound(alls.begin(), alls.end(), x) - alls.begin() + 1;
}
int main()
{
// 输入数组长度和查询次数
cin >> n >> q;
// 读入原始数组,并收集需要离散化的值
for (int i = 1; i <= n; i++)
{
cin >> a[i];
alls.push_back(a[i]); // 将数组元素加入离散化列表
}
// 读入查询区间,并收集查询端点用于离散化
for (int i = 1; i <= q; i++)
{
int l, r;
cin >> l >> r;
queries.push_back({l, r}); // 存储查询区间
alls.push_back(l); // 将左端点加入离散化列表
alls.push_back(r); // 将右端点加入离散化列表
}
// 离散化处理
sort(alls.begin(), alls.end()); // 排序
alls.erase(unique(alls.begin(), alls.end()), alls.end()); // 去重
// 使用树状数组统计离散化后的值
for (int i = 1; i <= n; i++)
{
int pos = find(a[i]); // 将原始值映射到离散化位置
add(pos, 1); // 在树状数组中对应位置加1
}
// 处理每个查询
for (auto t : queries)
{
// 将查询区间端点离散化
int l = find(t.first); // 左端点离散化
int r = find(t.second); // 右端点离散化
// 计算区间和:query(r) - query(l-1)
cout << query(r) - query(l - 1) << endl;
}
return 0;
}
【运行结果】
4 6
3 2 7 5
2 3
2 4
2 5
2 7
4 6
8 10
2
2
3
4
1
0
浙公网安备 33010602011771号