有从
1
1
1到
2
n
2n
2n一共
2
n
2n
2n个数字,让你将这
2
n
2n
2n个数字分成
n
n
n组,每组有两个数字。对于这
n
n
n组数字,你可以从中挑选
x
x
x组做
m
i
n
min
min操作,其他的
n
−
x
n-x
n−x组中做
m
a
x
max
max操作,这样就可以得到一个新的数组
b
b
b; 现在题目给你得到的数组
b
b
b,问你可以有多少不同的
x
x
x使得可以得到数组
b
b
b。
思路:
我们从这
2
n
2n
2n个数字中去掉数组
b
b
b中的数,剩下的就是数组
a
a
a中的数。对
a
a
a,
b
b
b数组排序之后,我们现在先枚举每个
x
x
x让
a
a
a中最大的
x
x
x个数字从小到达与
b
b
b中最小的x个数字从小到大组合,让
a
a
a中剩余的
n
−
x
n-x
n−x个数字从小到大与
b
b
b中剩余的
n
−
x
n-x
n−x个数字从小到大组合(类似于贪心的思想),这个应该是最优的情况,如果这样还是不能通过前
x
x
x个取
m
i
n
min
min后
n
−
x
n-x
n−x取
m
a
x
max
max得到数组
b
b
b,那么对于这个
x
x
x无论你再怎么组合都不可能得到数组
b
b
b。
从理论上来说上面这种枚举+贪心的方法肯定能得到最终的答案,但是时间复杂度达到了
o
(
n
2
)
o(n^2)
o(n2),这是不能接受的。我们再仔细分析一下,会发现符合要求的
x
x
x是连续的、在一个区间里面的,原因如下:
我们假设符合要求的
x
x
x的区间为
[
L
,
R
]
[L, R]
[L,R]。现在我们将
x
=
R
x=R
x=R情况对应的组合进行操作可以得到
x
=
R
+
1
x=R+1
x=R+1的情况:将数组
a
a
a中
n
−
x
n-x
n−x个最小的数字中最大的一个数字(称它为
i
i
i)与数组
b
b
b中
x
x
x个最小的数字中最小的一个数字(称它为
j
j
j)进行组合,这时候一定是因为
i
<
j
i<j
i<j从而取
m
i
n
min
min操作时不能得到
j
j
j所以不符合条件。而对于之后的
x
=
R
+
1
,
.
.
.
,
x
=
n
x=R+1, ..., x=n
x=R+1,...,x=n的情况,
b
b
b中
x
x
x个数字最小的数字中最小的数字是不变的,而
a
a
a中
x
x
x个最小的数字是不断变小的,所以之后的情况也都是不符合的。同理我们也可以从
x
=
L
−
1
,
.
.
.
,
x
=
0
x=L-1,..., x=0
x=L−1,...,x=0这些情况中得到同样的结论。
通过这个结论,我们可以通过两次二分查找,找到符合条件的
x
x
x区间
[
L
,
R
]
[L, R]
[L,R]的
L
L
L和
R
R
R,这样就可以将时间复杂度优化到
o
(
n
l
o
g
n
)
o(nlogn)
o(nlogn)。
AC代码
#include <cstdio>
#include <cstring>
#include <algorithm>
const int maxn = 2e5 + 5;
int a[maxn], b[maxn];
int check(int mid, int n) { // 0 suit; 1 l = mid + 1; 2 r = mid - 1;
for (int i = 0; i < mid; i++) {
if (b[i] > a[n - mid + i]) {
return 2;
}
}
for (int i = 0; i < n - mid; i++) {
if (a[i] > b[mid + i]) {
return 1;
}
}
return 0;
}
int main() {
int T, n;
scanf("%d", &T);
while (T--) {
scanf("%d", &n);
for (int i = 0; i < n; i++) {
scanf("%d", &b[i]);
}
std::sort(b, b + n);
int tot = 0, cur = 0;
for (int i = 0; i < 2 * n; i++) {
if (i + 1 == b[cur]) {
cur++;
} else {
a[tot++] = i + 1;
}
}
int l = 0, r = n;
while (l <= r) {
int mid = (l + r) >> 1;
if (check(mid, n) != 1) {
r = mid - 1;
} else {
l = mid + 1;
}
}
int L = l;
l = 0, r = n;
while (l <= r) {
int mid = (l + r) >> 1;
if (check(mid, n) != 2) {
l = mid + 1;
} else {
r = mid - 1;
}
}
int R = r;
printf("%d\n", R - L + 1);
}
return 0;
}