贪心、单调队列滑动窗口

LINK

给定一个长度n = 1e5 的数组A,元素是[-1e5, 1e5]之间的
给定一个tot = 1e10,表示最多可操作的次数
一次操作为:   将任意元素 += 1
  ' 注意,只有加法, 没有减法!!! '
问, 最终数组的 最大相同元素个数

[1, 2, 2, 2, 3]    tot = 2
' 你不可以变成[2,2,2,2,2]!!! '
' 操作只有加法,没有减法!!! 最优是:[2,2,2,2, 3] '
  即,ans = 4  (只使用一次操作)

' 一定要注意:  操作只有加法, 没有减法!! '
  这个题当然是很难的... 
  因为你很容易,和另一个题型混淆
  (给定一个数组,操作是:可以加+1、可以减-1,问让所有元素相同的 最少次数)
  ' 看似感觉这两个题很相似,但其实毫无关系 '
  (这道题是: 在<=limit的限制下,让相同元素 '尽可能的多')
  (而那个题是: 没有limit的限制, 而且是让 '所有的元素'都必须相同)
   ' 而且是最 致命的区别: 这道题只有【加法】,而那个题【加减法都有】 '
  (那个题的做法是: [10021}
  (我们获取他的'中位数' (通过sort 或 动态中位数)
  (即 2 是他的中位数, 最少操作是 (100-2)+(2-1)

将数组进行sort, 假设最终数组的ans 所对应的元素是X
1, X一定是最初数组里的元素
2, 最终数组里这ans个X元素,所对应的 最初数组里的元素
	I:  要么就是X (即不需要进行操作)
	II: 要么一定是Y (且Y<X) ' 即需要X-Y个操作 '
	'这个II,是非常重要的!!  因为元素只能变大,不能变小!!'

sort后的A是: [1, 1, 3, 3, 7, 7, 9]
  比如我们假设9,是最终的答案(且ans = 4)
  那么,答案数组一定是:[1, 1, 3, 9, 9, 9, 9]
  ' 即假设A[i]是答案, 则我们贪心的、就近原则 '
  ' 从A[i-1]、A[i-2]、A[i-3]、、、 依次的选 '
  '   直到耗尽tot为止   '
  这就很像 单调队列(因为对于A[i]来说,能和他匹配的 都是左侧的)
然后,我们需要证明: 单调性
 即比如,对于A[5]来说, 他往左走的极限是 A[2]{A[2, 3, 4, 5]最终,都可以变成A[5]}
  对于任意一个A[>5]来说,他往左走的极限一定是 A[>=2]' 只要证明这一点, 就说明  是单调滑动窗口 '
 [lef, ...., cur, nex]
 lef是对于cur来说,往左走的极限位置
   他的操作数 = (A[cur] - A[lef]) + (A[cur] - A[lef+1]) + ...  {共cur-lef项}
    <=  tot
   那么对于nex= cur+1来说:
      ' 假设nex所对应的 左端的,依然是lef '
      其操作数 = (A[nex] - A[lef]) + (A[nex] - A[lef+1]
      				+ .. + (A[nex] - A[cur])
	 {共cur-lef +1}
	 因为A[nex] >= A[cur],
	 所以可以发现, 此时的操作数 一定是>= 之前的操作数。
即,是单调的。

对于cur的操作数 = (cur-lef)*A[cur] - (A[lef]+A[lef+1]+...+A[cur-1])
这个式子, A[cur]是变量(不用维护他,每次O(1)临时计算即可)
后面这个区间和, 我们动态维护。
   
sort(A.begin(), A.end()); ' 重点 '

int lef = 0;
LL sum = 0; ' sum为: A[lef]+A[lef+1]+..+A[cur-1] '
' 对应式子: (cur-lef)*A[cur] - sum '
FOR(i, 0, n-1, 1){
	while( true ){
	    LL cur = (i - lef) * 1ll * A[i];
		   cur -= sum; ' cur 即为 操作数(代价) '
    	if(cur <= k) break;

	    sum -= A[lef]; ' 否则,lef右移 以减少代价 '
		++ lef;                
	}
MAX(ans, i - lef + 1);

sum += A[i];  ' 注意别忘了这里!!! sum是cur左侧的区间和'
	' 你每次右移动i, sum都要累加的!! '
}
return ans;
      


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值