题面
给定一个长为 n 的序列 a。
定义一个 n 个点的无向完全图,点 l 和点 r 之间的距离为 m i n i ∈ [ l , r ] { a i } min_{i \in [l, \; r]}\{a_i\} mini∈[l,r]{ai}
你可以进行 k 次操作,每次操作可以选定 ∀ i ∈ [ 1 , n ] \forall i \in [1,n] ∀i∈[1,n] 并将 a i a_i ai 赋值为一个 [ 1 , 1 0 9 ] [1, \; 10^9] [1,109] 的整数。请最大化这个图的直径。
定义 d x , y d_{x, \; y} dx,y 为 x,y 间的最短路径长度,则图的直径为 m a x x , y ∈ [ 1 , n ] { d x , y } max_{x, \; y \in [1, \; n]}\{d_{x, \; y}\} maxx,y∈[1,n]{dx,y}。即最短路径的最大值。
输出最大化的直径长度。
——翻译来自洛谷
分析
首先,我们发现改数的时候改为 1e9 始终会是最优的。
对于两点之间的距离
由于边权的性质,我们可以发现,不论图长什么 P 样子,对于两点 (x, y) 之间的最短路径,仅有两种情况:
- 走一条边,即 m i n i ∈ [ x , y ] { a i } min_{i \in [x, \; y]}\{a_i\} mini∈[x,y]{ai};
- 走两次最短边,即 2 × m i n i ∈ [ 1 , n ] { a i } 2 \times min_{i \in [1, \; n]}\{a_i\} 2×mini∈[1,n]{ai} (假设最小的 a 在 p 位置,那么我们只需要让 x 到 p 再从 p 到 y 即可)。
考虑证明:如果只走 x ,y 之间的边,则那些边都将大于等于其最短边,而且还要加,显然不优,走两边的就更显然了,证毕。
对于图的直径
对于上面第 2 种情况,显然对于任意两点都是确定的。
而对于第 1 种情况,显然当 |y - x| 增大时会减小,那么我们要取全图最大,就只需要考虑相邻的点即可。
因此我们得出图的直径等于: m a x i ∈ [ 1 , n ) { m i n { m i n { a i , a i + 1 } , 2 × m i n j ∈ [ 1 , n ] { a j } } } max_{i \in [1, n)} \{\;\;min\{ \; min\{ a_i, \; a_{i + 1} \}, \; 2 \times min_{j \in [1, \; n]}\{a_j\} \; \}\;\;\} maxi∈[1,n){min{min{ai,ai+1},2×minj∈[1,n]{aj}}}
看到这个 max 套 min 的形式,我们就可以想到第一种解法了:二分答案
解法1
我们考虑二分答案为 mid,考虑如何 Check。
我们分为两步考虑:
- 对于所有的 2 × a i < m i d 2 \times a_i < mid 2×ai<mid ,我们都需要将它进行赋值操作,否则我们所有的点对都可以通过他构成更短路。当然赋值为 1 0 9 10^9 109 是最好的,如果操作数量不足,则直接 return false;
- 如果执行完 1 操作后,我们发现只需要存在一组 m i n { a i , a i + 1 } ⩾ m i d min\{ a_i, \; a_{i + 1}\} \geqslant mid min{ai,ai+1}⩾mid 即可,那么此时如果已经满足条件,则 return true。若剩余步数 == 1 则跳至步骤 3,若 > 1 则跳至步骤 4,否则跳至步骤 5;
- 对于执行完 1 操作后 (我们令操作完的序列为 tmp) 只剩余 1 次操作机会的,则只要此时存在一个 t m p i ⩾ m i d tmp_i \geqslant mid tmpi⩾mid,我们只需更改它两边的任意一个即可 return true,否则 return false;
- 对于执行完 1 操作后 (我们令操作完的序列为 tmp) 剩余 > 1 次操作机会的,我们选择任意两个相邻的更改即可,因此始终 return false;
- return false。
算法复杂度 O ( n l o g S ) O(n log_S) O(nlogS) 其中 S 为值域 ( 1 0 9 10^9 109)。
AC代码
//省略快读和头文件
int T;
int n, k;
int a[MAXN], tmp[MAXN];
int mx;
bool Check(int ans)
{
for(int i = 1; i <= n; i++)
tmp[i] = a[i];
int cnt = 0, mx = 0;
for(int i = 1; i <= n; i++) {
if(2 * tmp[i] < ans) {//对应步骤1
tmp[i] = INF;
cnt++;
}
mx = max(mx, tmp[i]);
}
if(cnt > k)
return false;
for(int i = 1; i < n; i++)
if(min(tmp[i], tmp[i + 1]) >= ans)//对应步骤2
return true;
if(cnt + 1 == k)//对应步骤3
if(mx >= ans) {
return true;
}else {
return false;
}
if(cnt + 1 < k)//对应步骤4
return true;
return false;//对应步骤5
}
int main()
{
T = inpt();
while(T--) {
mx = INCF;
n = inpt(), k = inpt();
for(int i = 1; i <= n; i++) {
a[i] = inpt();
mx = max(mx, a[i]);
}
if(k >= n) {
puts("1000000000");
continue;
}
int l = 1, r = INF;
while(l < r) {
int mid = (l + r + 1) >> 1;
if(Check(mid))
l = mid;
else
r = mid - 1;
}
printf("%d\n", l);
}
return 0;
}
解法2
根据官方题解,我们还有一种聪明的贪心做法,时间复杂度更加优秀,但我还没看懂,以后再来补坑。。。