题解:AtCoder AT_awc0129_c Darts with Bonus Area
【题目来源】
AtCoder:C - Darts with Bonus Area
【题目描述】
Takahashi is in charge of scoring at a darts tournament.
In this tournament, Aoki throws darts a total of \(N\) times. For the \(i\)-th throw, the distance \(D_i\) from the center of the dartboard to where the dart lands is recorded.
This darts tournament has a special scoring rule. On the dartboard, \(M\) "bonus areas" are defined. The \(j\)-th bonus area is a ring-shaped region consisting of all points whose distance from the center of the dartboard is at least \(L_j\) and at most \(R_j\). Note that bonus areas may overlap with each other.
The score for each throw is calculated as follows:
- If the distance \(D_i\) from the center to where the dart lands is contained in at least one of the \(M\) bonus areas (that is, if there exists at least one \(j\) such that \(L_j \leq D_i \leq R_j\)), the score for that throw is \(2 \times D_i\). Even if the dart is contained in multiple bonus areas simultaneously, the score remains \(2 \times D_i\).
- If it is not contained in any bonus area, the score for that throw is \(D_i\).
Given the results of Aoki's \(N\) throws, calculate the total score on behalf of Takahashi.
高橋负责一场飞镖比赛的计分工作。
在这场比赛中,青木总共投掷飞镖 \(N\) 次。第 \(i\) 次投掷记录了飞镖落点与靶心之间的距离 \(D_i\)。
这场飞镖比赛有一个特殊的计分规则。在靶盘上定义了 \(M\) 个"加分区域"。第 \(j\) 个加分区域是一个环形区域,由所有与靶心距离至少为 \(L_j\) 且至多为 \(R_j\) 的点组成。注意,加分区域之间可能相互重叠。
每次投掷的得分按以下方式计算:
- 如果飞镖落点与靶心的距离 \(D_i\) 被至少一个加分区域包含(即存在至少一个 \(j\) 满足 \(L_j \leq D_i \leq R_j\)),则该次投掷的得分为 \(2 \times D_i\)。即使飞镖同时被多个加分区域包含,得分仍为 \(2 \times D_i\)。
- 如果它不被任何加分区域包含,则该次投掷的得分为 \(D_i\)。
给定青木 \(N\) 次投掷的结果,代替高橋计算总得分。
【输入】
\(N\) \(M\)
\(D_1\) \(D_2\) \(\ldots\) \(D_N\)
\(L_1\) \(R_1\)
\(L_2\) \(R_2\)
\(\vdots\)
\(L_M\) \(R_M\)
- The first line contains an integer \(N\) representing the number of throws and an integer \(M\) representing the number of bonus areas, separated by a space.
- The second line contains integers \(D_1, D_2, \ldots, D_N\) representing the distances from the center of the dartboard to where the dart landed for each throw, separated by spaces.
- The next \(M\) lines (from line \(3\) to line \(2+M\)) give the range of each bonus area.
- The \((2 + j)\)-th line (\(1 \leq j \leq M\)) contains the lower bound \(L_j\) and upper bound \(R_j\) of the \(j\)-th bonus area as integers separated by a space.
【输出】
Output the total score as an integer on a single line.
【输入样例】
3 2
5 10 15
3 7
12 20
【输出样例】
50
【核心思想】
-
问题分析:给定 \(N\) 次飞镖投掷距离 \(D_i\) 和 \(M\) 个环形加分区域 \([L_j, R_j]\)。若 \(D_i\) 被至少一个区域包含则得 \(2 \times D_i\),否则得 \(D_i\)。求总得分。这是一个区间合并 + 二分查找问题,关键在于将重叠的加分区域合并为不重叠区间,然后对每个 \(D_i\) 二分判断是否在合并后的某个区间内。
-
算法选择:
- 区间合并(Interval Merging):将 \(M\) 个可能重叠的区间按左端点排序后合并为 \(cur\) 个不重叠区间
- 二分查找(Binary Search):对每个 \(D_i\),在合并后的区间中二分查找是否存在包含 \(D_i\) 的区间
- 单次查询优化:合并后区间不重叠且有序,二分查找可将每次判断从 \(O(M)\) 优化到 \(O(\log cur)\)
-
关键步骤:
- 读入数据:\(N, M\),数组 \(d[1..N]\),区间数组 \(a[1..M]\)
- 区间合并:
- 按 \(l\) 升序排序 \(a\)
- 遍历排序后的区间:若当前区间与
merged[cur]不重叠(\(l > merged[cur].r\)),则新增区间;否则扩展右端点merged[cur].r = max(merged[cur].r, r)
- 计算总得分(遍历 \(i\) 从 \(1\) 到 \(N\)):
- 二分查找:在
merged[1..cur]中找最后一个满足merged[mid].l <= d[i]的区间left - 判断:若
cur > 0 && merged[left].l <= d[i] <= merged[left].r,则ok = true - 累加得分:
tot += ok ? 2*d[i] : d[i]
- 二分查找:在
- 输出 \(tot\)
-
时间/空间复杂度:
- 时间复杂度:\(O(M \log M + N \log M)\),排序 \(O(M \log M)\),\(N\) 次二分查找 \(O(N \log M)\)
- 空间复杂度:\(O(M)\),存储原始区间和合并后的区间
-
区间合并 + 二分查找的核心思想:
- 降维消除重叠:多个重叠区间合并后,任意点被覆盖的判断转化为"该点是否在某个合并区间内部",消除了多区间重复判断的复杂性
- 有序性支撑二分:合并后的区间按左端点有序且不重叠,保证了"若 \(D_i\) 在某区间内,则该区间是最后一个左端点 \(\leq D_i\) 的区间"这一关键性质
- 向上取整防死循环:二分查找使用
(left + right + 1) / 2向上取整,配合left = mid的更新方式,确保查找最后一个满足条件的元素时不会死循环 - 适用于"多个区间判定 + 大量查询"的问题,核心在于预处理合并区间,将 \(O(M)\) 单次查询优化为 \(O(\log M)\)
【算法标签】
整数二分
【代码详解】
#include <bits/stdc++.h>
using namespace std;
#define int long long // 将int定义为long long,避免总得分溢出
const int N = 200005; // 定义数组最大容量为200005
int n, m; // n为投掷次数,m为加分区域数量
int d[N]; // d[i]为第i次投掷的距离
int cur; // cur记录合并后的区间数量
struct Node
{
int l, r; // l为环形区域的内半径,r为外半径
} a[N], merged[N]; // a存储原始加分区域,merged存储合并后的不重叠区间
// 自定义排序规则:按左端点升序排列
bool cmp(Node x, Node y)
{
return x.l < y.l; // 按内半径从小到大排序
}
signed main() // 使用signed main配合#define int long long
{
cin >> n >> m; // 读入投掷次数n和加分区域数量m
for (int i = 1; i <= n; i++) // 读入每次投掷的距离
cin >> d[i];
for (int i = 1; i <= m; i++) // 读入每个加分区域的范围
cin >> a[i].l >> a[i].r;
sort(a + 1, a + m + 1, cmp); // 按左端点升序排序所有加分区域
// 区间合并:将重叠或相邻的加分区域合并为不重叠的区间
for (auto x : a) // 遍历排序后的所有区间(注意:这里遍历的是a[0]到a[m],可能有越界问题)
{
int l = x.l, r = x.r;
// 如果当前区间与上一个合并后的区间不重叠(当前左端点大于上一个的右端点)
if (cur == 0 || l > merged[cur].r)
merged[++cur] = {l, r}; // 新增一个合并区间
else
merged[cur].r = max(merged[cur].r, r); // 合并:扩展右端点
}
int tot = 0; // tot记录总得分
// 对每次投掷,二分查找判断距离是否在某个加分区域内
for (int i = 1; i <= n; i++) // 遍历每次投掷
{
int left = 1, right = cur; // 在合并后的区间中二分查找
// 二分查找最后一个满足merged[mid].l <= d[i]的区间
while (left < right)
{
int mid = (left + right + 1) / 2; // 向上取整,避免死循环
if (merged[mid].l <= d[i]) // 如果mid区间的左端点不大于d[i]
left = mid; // d[i]可能在mid或更右的区间中
else
right = mid - 1; // d[i]在mid左边
}
// 判断d[i]是否在找到的区间内
bool ok = false;
// 如果存在合并区间,且d[i]在left区间的[l,r]范围内
if (cur > 0 && merged[left].l <= d[i] && d[i] <= merged[left].r)
ok = true; // 该次投掷被加分区域包含
// 计算得分:被包含得2*D_i,否则得D_i
if (ok)
tot += 2 * d[i]; // 加分区域包含,得分为2倍距离
else
tot += d[i]; // 未被包含,得分为距离本身
}
cout << tot << endl; // 输出总得分
return 0;
}
【运行结果】
3 2
5 10 15
3 7
12 20
50
浙公网安备 33010602011771号