CF1712D Empty Graph 题解

21 篇文章 0 订阅

题面

给定一个长为 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) 之间的最短路径,仅有两种情况:

  1. 走一条边,即 m i n i ∈ [ x ,    y ] { a i } min_{i \in [x, \; y]}\{a_i\} mini[x,y]{ai}
  2. 走两次最短边,即 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。

我们分为两步考虑:

  1. 对于所有的 2 × a i < m i d 2 \times a_i < mid 2×ai<mid ,我们都需要将它进行赋值操作,否则我们所有的点对都可以通过他构成更短路。当然赋值为 1 0 9 10^9 109 是最好的,如果操作数量不足,则直接 return false;
  2. 如果执行完 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;
  3. 对于执行完 1 操作后 (我们令操作完的序列为 tmp) 剩余 1 次操作机会的,则只要此时存在一个 t m p i ⩾ m i d tmp_i \geqslant mid tmpimid,我们只需更改它两边的任意一个即可 return true,否则 return false;
  4. 对于执行完 1 操作后 (我们令操作完的序列为 tmp) 剩余 > 1 次操作机会的,我们选择任意两个相邻的更改即可,因此始终 return false;
  5. 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

根据官方题解,我们还有一种聪明的贪心做法,时间复杂度更加优秀,但我还没看懂,以后再来补坑。。。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值