ABC440D Forbidden List 2

abc440d---forbidden-list-2">ABC440D - Forbidden List 2

1. 题意

给定一个长度为 NN 的整数列表 AA,其中所有 AiA_i 互不相同。

对于每个询问 (X,Y)(X,Y),要求在所有大于等于 XX 且不在列表 AA 中的整数里,找到第 YY 小的数。

N,Q≤3×105N,Q \le 3\times 10^5,1≤Ai,X,Y≤1091\le A_i,X,Y\le 10^9。

2. 思路

这个数据规模,直接从 XX 开始枚举肯定会 TLE,所以我的第一反应是二分答案。

对于一个整数 vv 来说,设 f(v)=v−∣{ai∣ai≤v}∣f(v) = v - |\{ a_{i} \mid a_{i} \le v \}| 表示不超过 vv 的正整数中,有多少个整数不在 AA 数组中。

如果直接二分答案 ansans,那么对于询问 (X,Y)(X, Y),需要找到最小的 ansans,满足区间 [X,ans][X, ans] 中至少有 YY 个缺失数,也就是 f(ans)−f(X−1)≥Yf(ans) - f(X - 1) \ge Y。

这个做法是可行的,但还可以继续优化。继续观察这个式子可以发现,每次询问真正需要的并不是区间本身,而是缺失数在全体正整数中的排名。

设小于 XX 的缺失数的个数为 missmiss,那么大于等于 XX 的第 YY 个缺失数,就等价于全体正整数中的第 miss+Ymiss + Y 个正整数。

因此对于询问 (X,Y)(X, Y),先计算 k=miss+Yk = miss + Y,然后就把问题变成了:求全体正整数中第 kk 个缺失数。

将 AA 排序后,考虑第 ii 个已有数字 aia_i。

在 11 到 aia_i 之间,一共有 aia_i 个正整数,其中有 ii 个数出现在列表 AA 中,所以不超过 aia_{i} 的缺失数数量为:ai−ia_i-i,我们将其记为 bb 数组:bi=ai−ib_{i} = a_{i} - i。

由于 aia_i 严格递增,有:

bi+1−bi=(ai+1−(i+1))−(ai−i)=ai+1−ai−1≥0b_{i+1}-b_i=(a_{i+1}-(i+1))-(a_i-i)=a_{i+1}-a_i-1\ge 0

所以数组 bb 是单调不降的,可以对它进行二分。

对于一次询问 (X,Y)(X,Y),先在 AA 中找到第一个满足 aposx≥Xa_{posx}\ge X 的位置,记为 posx。

由于我的数组采用 11 下标,所以小于 XX 的已有数字个数为 posx−1posx-1,小于 XX 的正整数总数为 X−1X-1。

因此小于 XX 的缺失数个数为:miss=(X−1)−(posx−1)=X−posxmiss = (X - 1) - (posx - 1) = X - posx。

目标就是全体正整数中的第:k=miss+Yk = miss + Y 个缺失数。

接下来在 bb 中找到第一个满足 bpos≥kb_{pos}\ge k 的位置,记为 pos:

因为 bposb_{pos} 表示不超过 aposa_{pos} 的缺失数数量,所以第 kk 个缺失数一定在 apos−1a_{pos-1} 到 aposa_{pos} 之间。

设答案为 ansans,在 11 到 ansans 中,一共有:kk 个缺失数;pos−1pos-1 个出现在列表 AA 中的数。

所以:ans=k+(pos−1)ans = k + (pos - 1)

最终复杂度为:O(Nlog⁡(N)+Qlog⁡(N))\mathcal{O}(N \log(N) + Q \log(N))。

3. 代码部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
ll read( ) { /* 模板代码略 */ }

ll a[maxn], b[maxn];

void solve( ) {
int n = read( ), q = read( );
for( int i = 1; i <= n; ++ i ) a[i] = read( );
sort( a + 1, a + n + 1 );
for( int i = 1; i <= n; ++ i ) b[i] = a[i] - i;

    for( int i = 1; i <= q; ++ i ) {
ll x = read( ), y = read( );

// posx 是第一个 >= x 的位置
int posx = lower_bound( a + 1, a + n + 1, x ) - a;

ll miss = x - posx; // 小于 x 的缺失数字个数:
ll k = y + miss;

// 找第一个使得缺失数量达到 k 的 a[pos]
int pos = lower_bound( b + 1, b + n + 1, k ) - b;

// 第 k 个缺失数之前有 pos - 1 个已有数字
ll ans = k + ( pos - 1 );

cout << ans << '\n';
}
}

4. 复盘

  • 复杂度分析:排序 O(Nlog⁡N)O(N\log N),每次询问 O(log⁡N)O(\log N),总复杂度 O(Nlog⁡N+Qlog⁡N)O(N\log N+Q\log N)。

  • 关键转化:将“大于等于 XX 的第 YY 个缺失数”转化为“全体正整数中的第 kk 个缺失数”。

  • 关联笔记:浅谈二分答案


ABC440D Forbidden List 2
http://miqiu.space/posts/7f795164/
作者
Miqiu
发布于
2026年7月6日
许可协议