NC224938 加减

枚举 二分 前缀和

给定可以改变的次数k,要求改变后出现次数最多的数,那么一定是由该数“附近”元素构成的一段连续子序列花完k次变化后得到。或者可以说,k是这一段连续子序列到目标值的距离之和

所以主要的解题思路:

  • 原数组排序,求前缀和
  • 枚举左端点L
  • 二分右端点R
  • 目标数一定是L和R区间内的中位数(数学规律,试想目标值偏小或偏大都会导致距离和变大)
  • 前缀和求出 [ L ,R ] 区间元素到中位数的距离之和,与k比较

AC代码:

#include<bits/stdc++.h> 
using namespace std;

typedef long long int ll;

ll n, k;
ll a[100005];       // 原数组
ll preSum[100005];  // 前缀和 prefix Sum
int ans;

ll cal(int L,int R){
    int mid = (L + R) / 2;
    //前缀和求距离和
    return preSum[R] - preSum[mid] - preSum[mid - 1] + preSum[L - 1] - a[mid] * (R - mid) + a[mid] * (mid - L);
}

int main(){
    scanf("%lld%lld", &n, &k);
    for (int i = 1; i <= n; i++)
        scanf("%lld", &a[i]);
    sort(a + 1, a + n + 1);         // 排序
    for (int i = 1; i <= n; i++)    // 求前缀和
        preSum[i] = preSum[i - 1] + a[i];
    for (int i = 1; i <= n; i++){   // 枚举区间左端点
        int l = i, r = n; 
        while(l<=r){                // 二分右端点
            int mid = (l + r) / 2;
            if(cal(i,mid)<=k)
                l = mid + 1, ans = max(ans, mid - i + 1);
            else
                r = mid - 1;
        }
    }
    printf("%d\n", ans);
    return 0;
}