视频加载失败

安吉D5-T3

2645 字
13 分钟
安吉D5-T3
题目呈现

P9100 Miny#

题目描述#

nn 枚地雷被运到 Bytau 的军事训练场,并沿一条直线埋设.每个地雷位于不同的地方,并且有自己的爆炸半径.当引爆时,地雷会自动引爆其爆炸半径内所有尚未爆炸的地雷.如果地雷 aa 和地雷 bb 之间的距离不超过地雷 bb 的爆炸半径,则我们称地雷 aa 在地雷 bb 的爆炸半径内.

Bytomir 中士想进行一项实验.他选择了一个任意的地雷子集(也许是空的),并让这个地雷子集内的所有地雷在同时手动引爆.实验的结果是一组已经爆炸的地雷——要么是手动引爆的引起的爆炸,要么是其他地雷爆炸导致的爆炸.

Bytomir 能得到多少种可能的实验结果?如果两个实验结果中爆炸的地雷相同,则这两个实验结果是相同的.由于结果可能很大,请输出它除以 109+710^9+7 的余数.

输入格式#

输入第一行包含一个整数 nn,表示地雷个数.

接下来 nn 行,每行两个整数 ai,ria_i,r_i,分别表示地雷的位置和爆炸半径.你可以假设 a1<a2<<ana_1<a_2<\cdots<a_n

输出格式#

输出可能的实验结果总数对 109+710^9+7 取模后的值.

输入输出样例 #1#

输入 #1#

4
0 2
2 0
3 2
7 4

输出 #1#

7

说明/提示#

样例 1 解释#

你可以得到 77 种可能的实验结果:

  • {}\{\}(空集):如果不引爆任何地雷;
  • {1,2}\{1,2\}(地雷 1,21,2):如果我们只引爆地雷 11
  • {1,2,3}\{1,2,3\}:如果我们引爆地雷 1133
  • {1,2,3,4}\{1,2,3,4\}:如果我们引爆地雷 1144
  • {2}\{2\}:如果我们只引爆地雷 22
  • {2,3}\{2,3\}:如果我们只引爆地雷 33
  • {2,3,4}\{2,3,4\}:如果我们只引爆地雷 44

请注意,可以通过不同的方式得到同一个实验结果——例如,如果我们引爆地雷 1122,也会得到 {1,2}\{1, 2\} 的结果.


数据范围#

本题采用捆绑测试

对于 50%50\% 的数据,保证 1n50001\le n\le 5000

对于 100%100\% 的数据,保证 1n3×1051\le n\le 3\times 10^50ai,ri10180\le a_i,r_i\le 10^{18}

先来解决小数据.

由于小数据数据范围较少,考虑 dp,这里定义 dp[i] 表示考虑到第 ii 个地雷,且第 ii 个地雷不被引爆的方案数,这样 dp 可以使得第 ii 个地雷(及以前的所有地雷)都不会引爆第 ii 个地雷之后的所有地雷,从而满足无后效性.

显然,有初始状态:dp[0] = 1,即不选择地雷,则存在没有地雷爆炸这一种情况.

考虑转换方程:dp[i] 可以由 dp[j] 转换而来(j<ij<i),当且仅当手动引爆第 [i+1,j1][i+1,j-1] 个地雷时,地雷 iijj 都不会被引爆.

为了满足这两颗地雷都不会被引爆,我们需要保证:

  • 地雷 ii 左边第一个会引爆 ii 的地雷不在区间 [i+1,j1][i+1,j-1] 之中.(保证地雷 ii 不会被引爆)
  • 地雷 jj 右边第一个会引爆 jj 的地雷不在区间 [i+1,j1][i+1,j-1] 之中.(保证地雷 ii 不会被引爆)

即,设地雷 ii 左边第一个会引爆 ii 的地雷为 LiL_i,地雷 ii 右边第一个会引爆 ii 的地雷为 RiR_i.则符合条件的 jj 满足:

  • j<ij<i
  • LijL_i\leq j
  • iRji\leq R_j

,从 i1i-1 开始向前遍历直到 LiL_i,寻找合法的 jj 加入贡献即可.

在计算 LiL_i 时,从 i1i-1 开始向前遍历,找到第一个 aj+rjaia_j + r_j \geq a_i 的地雷,Li=jL_i=j.同理,在计算 RiR_i 时,从 i+1i+1 开始向前遍历,找到第一个 ajrjaia_j - r_j \leq a_i 的地雷,Li=jL_i=j

在统计最终状态时,可以引入哨兵,在地雷序列首位和末尾各增加一个距离无限远、爆炸半径无限远(可以炸到另外一个哨兵)的哨兵地雷 00n+1n+1.从而使得 i[1,n]\forall i \in [1, n]LiL_iRiR_i 都不为空,同时,dp[n+1] 还能收集到所有未贡献状态(即 j[0,n],Rj=n+1\forall j\in [0, n],R_j=n+1 的状态 dp[j]).

最终状态即为 dp[n+1]

50pts50pts 标程

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 3e5 + 100;
const int MOD = 1e9 + 7;
int n, a[N], r[N], dp[N], L[N], R[N];
signed main() {
#ifdef ONLINE_JUDGE
freopen("landmine.in", "r", stdin);
freopen("landmine.out", "w", stdout);
#endif
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i] >> r[i];
}
a[n + 1] = 2e18;
r[n + 1] = 4e18 + 10; // LLONG_MAX 约为 9e18
a[0] = -2e18;
r[0] = 4e18 + 10;
for (int i = 0; i <= n + 1; i++) {
for (int j = 0; j < i; j++) {
if (a[j] + r[j] >= a[i]) {
L[i] = j;
}
}
}
memset(R, 0x3f, sizeof(R));
for (int i = 0; i <= n + 1; i++) {
for (int j = i + 1; j <= n + 1; j++) {
if (a[j] - r[j] <= a[i]) {
R[i] = min(j, R[i]);
}
}
}
dp[0] = 1;
for (int i = 0; i <= n + 1; i++) {
for (int j = L[i]; j < i; j++) {
if (i <= R[j]) {
(dp[i] += dp[j]) %= MOD;
}
}
}
cout << dp[n + 1];
return 0;
}

这样的复杂度是 O(n2)O(n^2) 的,考虑如何优化.

首先,预处理 LiL_iRiR_i 可以使用 ST 表和二分进行优化.以计算 LiL_i 为例,具体来说,对于区间 [l,r][l,r],二分可能的子区间 i[mid,r]i\in [mid,r],对于该子区间,求出 a[i]+r[i]a[i]+r[i] 的最大值(即每个地雷最右影响位置)是否比 aia_i 大(或等于),如果是,说明 LiL_i 在该子区间内,在该子区间内继续二分,否则,在相反的子区间 [l,mid1][l, mid-1] 内进行二分.RiR_i 的处理和 LiL_i 相类似.

ST 表实现如下:

struct ST {
int *p;
int fmax[32][N];
int fmin[32][N];
ST(int *p) : p(p) {}
void init(int size) {
for (int i = 0; i <= size; i++) {
fmax[0][i] = p[i];
fmin[0][i] = p[i];
}
for (int j = 1; j <= 30; j++) {
for (int i = 0; i + (1 << (j - 1)) <= size; i++) {
fmax[j][i] =
max(fmax[j - 1][i], fmax[j - 1][i + (1 << (j - 1))]);
fmin[j][i] =
min(fmin[j - 1][i], fmin[j - 1][i + (1 << (j - 1))]);
}
}
}
int mini(int l, int r) {
int len = log(r - l + 1) / log(2);
return min(fmin[len][l], fmin[len][r - (1 << len) + 1]);
}
int maxi(int l, int r) {
int len = log(r - l + 1) / log(2);
return max(fmax[len][l], fmax[len][r - (1 << len) + 1]);
}
};
ST l_st(l_lim), r_st(r_lim);

预处理实现如下:

for (int i = 0; i <= n + 1; i++) {
l_lim[i] = a[i] - r[i];
r_lim[i] = a[i] + r[i];
}
l_st.init(n + 1), r_st.init(n + 1);
for (int i = 0; i <= n + 1; i++) {
int l = 0, r = i - 1, ans = 0;
while (l <= r) {
int mid = (l + r) / 2;
if (r_st.maxi(mid, r) >= a[i]) {
ans = mid;
l = mid + 1;
} else {
r = mid - 1;
}
}
L[i] = ans;
}
for (int i = 0; i <= n + 1; i++) {
int l = i + 1, r = n + 1, ans = n + 1;
while (l <= r) {
int mid = (l + r) / 2;
if (l_st.mini(l, mid) <= a[i]) {
ans = min(ans, mid);
r = mid - 1;
} else {
l = mid + 1;
}
}
R[i] = ans;
}

再考虑如何优化 dp.

dp 的转移方程中涉及到区间加和的问题,因此可以将 dpdp 数组放入树状数组优化.在每一次计算 dp[i]dp[i] 时,计算 j[Li,i1]j\in [L_i,i-1] 之和,再减去所有不满足 iRji\leq R_jjj

怎么减去呢?要减去的 jj 满足 Rj<iR_j < i,显然,当一项需要被减去之后(Rj=iR_j=i),该项就不会再对之后的计算产生贡献(Rji+k,kN+R_j \geq i+k,k\in\mathbb{N}^+).因此,在每一次计算完 dp[i]dp[i] 之后,将所有的 jj 满足 Rj=iR_j=idp[j]dp[j] 在树状数组中删除(即设为 0)(查找满足要求的 jj 可以使用 map),这样,在后续进行计算时,该项就不会被计入贡献.

map<int, vector<int>> r_R;
// 树状数组实现
struct BIT {
int c[N];
int size;
int lowbit(int i) { return i & (-i); }
BIT() { memset(c, 0, sizeof(c)); }
void init(int size) { this->size = size; }
void upd(int p, int v) {
p++; // 转为 1-based
for (int i = p; i <= size; i = i + lowbit(i)) {
(c[i] += v + MOD) %= MOD;
}
}
int qry(int p) {
p++; // 转为 1-based
int ans = 0;
for (int i = p; i >= 1; i -= lowbit(i)) {
(ans += c[i]) %= MOD;
}
return ans;
}
int qry(int l, int r) { return qry(r) - qry(l - 1); }
};
BIT bit;

main 函数内:

bit.upd(0, 1);
dp[0] = 1;
for (int i = 1; i <= n + 1; i++) {
(dp[i] += bit.qry(L[i], i - 1)) %= MOD;
bit.upd(i, dp[i]);
vector<int> &vec = r_R[i];
for (int j = 0; j < vec.size(); j++) {
int ori = bit.qry(vec[j], vec[j]);
bit.upd(vec[j], -ori);
}
}

100pts100pts 标程

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 3e5 + 100;
const int MOD = 1e9 + 7;
int n, a[N], r[N], dp[N], L[N], R[N], l_lim[N], r_lim[N];
map<int, vector<int>> r_R;
struct ST {
int *p;
int fmax[32][N];
int fmin[32][N];
ST(int *p) : p(p) {}
void init(int size) {
for (int i = 0; i <= size; i++) {
fmax[0][i] = p[i];
fmin[0][i] = p[i];
}
for (int j = 1; j <= 30; j++) {
for (int i = 0; i + (1 << (j - 1)) <= size; i++) {
fmax[j][i] =
max(fmax[j - 1][i], fmax[j - 1][i + (1 << (j - 1))]);
fmin[j][i] =
min(fmin[j - 1][i], fmin[j - 1][i + (1 << (j - 1))]);
}
}
}
int mini(int l, int r) {
int len = log(r - l + 1) / log(2);
return min(fmin[len][l], fmin[len][r - (1 << len) + 1]);
}
int maxi(int l, int r) {
int len = log(r - l + 1) / log(2);
return max(fmax[len][l], fmax[len][r - (1 << len) + 1]);
}
};
struct BIT {
int c[N];
int size;
int lowbit(int i) { return i & (-i); }
BIT() { memset(c, 0, sizeof(c)); }
void init(int size) { this->size = size; }
void upd(int p, int v) {
p++; // 转为 1-based
for (int i = p; i <= size; i = i + lowbit(i)) {
(c[i] += v + MOD) %= MOD;
}
}
int qry(int p) {
p++; // 转为 1-based
int ans = 0;
for (int i = p; i >= 1; i -= lowbit(i)) {
(ans += c[i]) %= MOD;
}
return ans;
}
int qry(int l, int r) { return qry(r) - qry(l - 1); }
};
ST l_st(l_lim), r_st(r_lim);
BIT bit;
signed main() {
#ifdef ONLINE_JUDGE
freopen("landmine.in", "r", stdin);
freopen("landmine.out", "w", stdout);
#endif
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i] >> r[i];
}
a[n + 1] = 2e18;
r[n + 1] = 4e18 + 10;
a[0] = -2e18;
r[0] = 4e18 + 10;
for (int i = 0; i <= n + 1; i++) {
l_lim[i] = a[i] - r[i];
r_lim[i] = a[i] + r[i];
}
l_st.init(n + 1), r_st.init(n + 1);
bit.init(n + 1);
for (int i = 0; i <= n + 1; i++) {
int l = 0, r = i - 1, ans = 0;
while (l <= r) {
int mid = (l + r) / 2;
if (r_st.maxi(mid, r) >= a[i]) {
ans = mid;
l = mid + 1;
} else {
r = mid - 1;
}
}
L[i] = ans;
}
for (int i = 0; i <= n + 1; i++) {
int l = i + 1, r = n + 1, ans = n + 1;
while (l <= r) {
int mid = (l + r) / 2;
if (l_st.mini(l, mid) <= a[i]) {
ans = min(ans, mid);
r = mid - 1;
} else {
l = mid + 1;
}
}
R[i] = ans;
r_R[R[i]].push_back(i);
}
bit.upd(0, 1);
dp[0] = 1;
for (int i = 1; i <= n + 1; i++) {
(dp[i] += bit.qry(L[i], i - 1)) %= MOD;
bit.upd(i, dp[i]);
vector<int> &vec = r_R[i];
for (int j = 0; j < vec.size(); j++) {
int ori = bit.qry(vec[j], vec[j]);
bit.upd(vec[j], -ori);
}
}
cout << dp[n + 1];
return 0;
}

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

安吉D5-T3
https://blog.jerrylab.top/posts/problem/anji2026/D5/T3/
作者
Jerry
发布于
2026-08-05
许可协议
CC BY-NC-SA 4.0
Profile Image of the Author
Jerry
Hello, I'm Jerry.
公告
欢迎来到我的博客!这是一则示例公告。
分类
标签
最新动态
站点统计
文章
80
分类
3
标签
35
总字数
89,198
运行时长
0
最后活动
0 天前
站点信息
构建平台
Cloudflare Pages
博客版本
Firefly v6.16.5
文章许可
CC BY-NC-SA 4.0