给定
n
n
n 个点的点权
a
i
a_i
ai,这
n
n
n 个点两两之间连边构成一个无向完全图,
e
d
g
e
(
l
,
r
)
edge(l,r)
edge(l,r) 的边权为
m
i
n
(
a
l
,
a
l
+
1
,
.
.
.
,
a
r
)
,
(
1
≤
l
<
r
≤
n
)
min(a_l, a_{l+1},...,a_r),(1\leq l
d
(
u
,
v
)
d(u,v)
d(u,v) 是点
u
u
u 和点
v
v
v 的最短路径,图上直径为最大的
d
(
u
,
v
)
d(u,v)
d(u,v)。
现在需要最大化直径,有
k
k
k 次修改点权的机会,问最大化的直径为多少。
数据范围:
1
≤
k
≤
n
≤
1
0
5
,
1
≤
a
i
≤
1
0
9
1\leq k\leq n\leq 10^5, 1\leq a_i\leq 10^9
1≤k≤n≤105,1≤ai≤109
二分答案为 x x x
对于任意两个点的
d
(
u
,
v
)
d(u,v)
d(u,v),不妨设
u
<
v
u
从
u
u
u 到
v
v
v 的最短路径可以从两部分考虑:
枚举
u
u
u 和
v
v
v,对于
a
u
,
a
u
+
1
,
.
.
.
a
v
a_u,a_{u+1},...a_v
au,au+1,...av,小于
x
x
x 的部分都需要一次操作,
对于剩余的点权
a
m
i
d
a_{mid}
amid,
a
m
i
d
×
2
<
x
a_{mid}\times 2
但是枚举两个点的复杂度是 O ( n 2 ) O(n^2) O(n2) 的,考虑如何继续优化
记
p
r
e
pre
pre 为前缀中
a
i
×
2
<
x
a_i\times 2 < x
ai×2<x 的数量
记
s
u
f
suf
suf 为后缀中
a
i
×
2
<
x
a_i\times 2 < x
ai×2<x 的数量
记
p
r
e
2
pre2
pre2 为前缀中
a
i
<
x
a_i
枚举
u
u
u 和
v
v
v ,使得
d
(
u
,
v
)
≥
x
d(u,v)\geq x
d(u,v)≥x 的操作次数为:
(
p
r
e
[
u
−
1
]
)
+
(
s
u
f
[
v
+
1
]
)
+
(
p
r
e
2
[
v
]
−
p
r
e
2
[
u
−
1
]
)
(pre[u-1])+(suf[v+1])+(pre2[v]-pre2[u-1])
(pre[u−1])+(suf[v+1])+(pre2[v]−pre2[u−1])
等价于
(
p
r
e
[
u
−
1
]
−
p
r
e
2
[
u
−
1
]
)
+
(
s
u
f
[
v
+
1
]
+
p
r
e
2
[
v
]
)
(pre[u-1]-pre2[u-1])+(suf[v+1]+pre2[v])
(pre[u−1]−pre2[u−1])+(suf[v+1]+pre2[v])
记录
m
i
n
u
minu
minu 为前缀中
p
r
e
[
u
−
1
]
−
p
r
e
2
[
u
−
1
]
pre[u-1]-pre2[u-1]
pre[u−1]−pre2[u−1] 的最小值
记录
m
i
n
v
minv
minv 为后缀中
s
u
f
[
v
+
1
]
+
p
r
e
2
[
v
]
suf[v+1]+pre2[v]
suf[v+1]+pre2[v] 的最小值
然后枚举
i
i
i 为分割点,判断是否存在一个
u
u
u,使得
m
i
n
u
[
i
]
+
m
i
n
v
[
i
+
1
]
≤
k
minu[i]+minv[i+1]\leq k
minu[i]+minv[i+1]≤k 即可
这里甚至可以记录
m
i
n
u
[
i
]
minu[i]
minu[i] 和
m
i
n
v
[
i
+
1
]
minv[i+1]
minv[i+1] 的最小值所在索引,从而确定对应的区间
[
u
,
v
]
[u,v]
[u,v]。
代码:
#include
using namespace std;
typedef long long ll;
const int N = 100010;
const int MAX = 1000000000;
int a[N], n, k;
int pre2[N];
int pre[N];
int suf[N];
int minj[N];
int mini[N];
bool check(int x) {
pre[0] = 0;
for (int i = 1; i <= n; ++i) {
pre[i] = pre[i - 1];
if (a[i] * 2 < x) pre[i] += 1;
pre2[i] = pre2[i - 1];
if (a[i] < x) pre2[i] += 1;
}
suf[n + 1] = 0;
for (int i = n; i >= 1; --i) {
suf[i] = suf[i + 1];
if (a[i] * 2 < x) suf[i] += 1;
}
// 找到一对(i, j)
// pre2[j] - pre2[i - 1] + suf[j + 1] + pre[i - 1];
// (pre2[j] + suf[j + 1]) + (pre[i - 1] - pre2[i - 1]);
mini[0] = MAX;
for (int i = 1; i <= n; ++i) {
mini[i] = min(mini[i - 1], pre[i - 1] - pre2[i - 1]);
}
minj[n + 1] = MAX;
for (int j = n; j >= 1; --j) {
minj[j] = min(minj[j + 1], pre2[j] + suf[j + 1]);
}
for (int i = 1; i + 1 <= n; ++i) {
if (mini[i] + minj[i + 1] <= k) return true;
}
return false;
}
void solve() {
scanf("%d%d", &n, &k);
for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);
int l = 1, r = MAX;
while (l < r) {
int mid = l + r + 1 >> 1;
if (check(mid)) l = mid;
else r = mid - 1;
}
printf("%d\n", l);
}
int main()
{
int T = 1;
scanf("%d", &T);
for (int i = 1; i <= T; ++i) {
solve();
}
return 0;
}