题目大意:数轴上有n个点,在总代价不超过m的情况下,至多可以将多少个点移到一起?(代价:每个点移动的距离)
数据范围500万,递增输入,不需要排序,显然需要用O(n)做法才能满分,而且读入还可能被卡。
能走到一起的,必然是连续的若干个点:如果一个区间中有个点没有移动过去,那么终点要么在他左边,要么在他右边;如果在左边,他右边的点移动过去的代价比他大,没必要;另外的方向同理。
区间平移:至少1各点,区间至少为2才判断;从区间[i=1, j=2]开始,如果能移到一起,那么j加1,尝试让更多的点移到一起;如果不能移到一起,那么左端点无论如何都无法跟j以及j右边的点移到一起了,i加1;边界:i跟j相等,让j加1,保存区间长度至少2;区间右端点超过n结束。
【验证】
1、贪心选位置:区间所有点移到同一个位置,如果是奇数个点,必然移到中间那个点的位置最好。中间点左边有x各点,右边也有x个点,如果位置左移,代价左边减少x,右边增加x+1,这个1的中间点本身;如果左边点转到右边去,代价会更大。同样的,如果是偶数个点,移到中间那两个点之间的位置都可以,证明同上。
2、求代价之和:对于区间[l, r],不妨设选中的点是k,位置是a[k],左边的代价是a[k]-a[l] + a[k]-a[l+1] ……,共k-l+1个,可以转成a[k]*(k-l+1)加上[l, k]的区间和;同样的,右边也可以这样做。
#include <bits/stdc++.h> #define LL long long #define N 5000005 using namespace std; LL n, m, i, j, k, ans=1, a[N], s[N]; LL du(){ LL n = 0; char c = getchar(); while(c <= 32) c = getchar(); while(c > 32){ n = n * 10 + c - 48; c = getchar(); } return n; } int ok(LL l, LL r){ LL i, j, k, x, y, d; k = l+r >> 1; x = k - l + 1, y = r - k; d = x*a[k] - (s[k]-s[l-1]); d += (s[r]-s[k]) - y*a[k]; return d <= m; } int main(){ scanf("%lld%lld", &n, &m); for(i=1; i<=n; i++){ // scanf("%lld", &a[i]); a[i] = du(); s[i] = s[i-1] + a[i]; } for(i=1, j=2; j<=n; ){ if(ok(i, j)){ if(++j-i > ans) ans = j-i; } else if(++i == j) j++; } printf("%lld\n", ans); return 0; }