洛谷_P8872

链接

  • 点此跳转
  • 参考博客

分析

假设经过 m m m 次操作后,剩余序列的值域为 [ l , r ] [l,r] [l,r] ,那么小于 l l l 的数和大于 r r r 的数一定被操作了。

设原序列中,小于 l l l 的数有 u u u 个,大于 r r r 的数有 v v v 个。

那么最小操作次数为 u + v + m i n ( u , v ) u + v + min(u, v) u+v+min(u,v)

证明:

不妨假设 u < v u < v u<v ,进行 u u u 次操作将原序列的最小值变为 l l l ,再进行 u + v u + v u+v 次操作将原序列的最大值变为 r r r

那么操作次数为 u + v + u u + v + u u+v+u 即为 u + v + m i n ( u , v ) u + v + min(u,v) u+v+min(u,v)

通俗一点理解为,先将数量小的一端变为另一端,然后再变回来。

具体操作:

先对序列升序排序。

枚举所有的 a i a_i ai 作为 l l l ,找到最小的 a j a_j aj 作为 r r r ,此时极值为 r − l r-l rl

由于 j ≥ i j \ge i ji ,因为 i i i 是递增的,所以 j j j 是单调不减的,因此时间复杂度瓶颈是排序 ( n l o g n nlogn nlogn) 。

如何找到 j j j 呢?

因为有 ( i − 1 ) + ( n − j ) + m i n ( i − 1 , n − j ) ≤ m (i-1)+(n-j)+min(i-1,n-j) \le m (i1)+(nj)+min(i1,nj)m ,所以让 j j j m a x ( i , j ) max(i,j) max(i,j) 开始循环,找到第一个满足条件的 j j j 即可。

AC代码

// #pragma GCC optimize(3)
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
// #include 
// #include 
#define endl '\n'
#define x first
#define y second
#define fi first
#define se second
#define PI acos(-1)
// #define PI 3.1415926
#define LL long long
#define INF 0x3f3f3f3f
#define lowbit(x) (-x&x)
#define PII pair<int, int>
#define ULL unsigned long long
#define PIL pair<int, long long>
#define all(x) x.begin(), x.end()
#define mem(a, b) memset(a, b, sizeof a)
#define rev(x) reverse(x.begin(), x.end())
#define IOS ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)

using namespace std;

const int N = 1e5 + 10;

int n, m;
int a[N], b[N];
int res = 2 * INF;

void solve() {
	cin >> n >> m;
	for (int i = 1; i <= n; i ++ ) {
		cin >> a[i];
	}
	
	sort(a + 1, a + 1 + n);
	
	int j = 1;
	for (int i = 1; i <= min(n, m + 1); i ++ ) {
		j = max(j, i);
		while ((i - 1) + (n - j) + min(i - 1, n - j) > m) ++ j;
		res = min(res, a[j] - a[i]);
	}
	
	cout << res << endl;
}

int main() {
	IOS;
	
	solve();
	
	return 0;
}

你可能感兴趣的:(贪心,算法)