[FZOJ183] 「2019冬令营提高组」排序(堆)

题意

  • 在选择排序的 n ( n − 1 ) 2 \rm{\frac{n(n-1)}{2}} 2n(n1)判断中,求出第 K \rm K K次判断后的序列。

我们可以求出第 K \rm K K次判断后执行了 k \rm k k轮的排序,那么剩下的一遍暴力做就好了。

考虑怎么求出执行 k \rm k k轮后的序列,我们依次确定第 k + 1 \rm{k + 1} k+1位到第 n \rm n n位的数,假设我们当前正在考虑第 i \rm i i位是什么,那么在 k \rm k k轮排序后第 i \rm i i个位置上的值是 1 1 1 i − 1 \rm{i-1} i1中第 k \rm{k} k小的值 t p \rm {tp} tp a i \rm a_i ai中较大的那一个,我们来证明一下这个为什么是对的。

假设执行了一轮,结论显然正确,那么假设执行了 k − 1 \rm {k-1} k1轮,我们来证明第 k \rm k k轮后的序列满足以上结论。

先要了解一个小结论:

  • k \rm k k轮排序后对于 ∀ i > k \rm{\forall i>k} i>k,区间 [ 1 , i − 1 ] \rm[1,i-1] [1,i1]的第 k \rm k k小的值与初始序列中 [ 1 , i − 1 ] \rm[1,i-1] [1,i1]的第 k \rm k k小值是相等的。
  • 这个应该不是很难证明,第 k \rm k k轮排序到 i \rm i i的过程,只是把 [ k , i − 1 ] \rm[k,i-1] [k,i1]的最小值放在了 a k \rm a_k ak的位置,在 i \rm i i前面的交换只是内部交换。而当前轮以后的交换过程,同样只会影响 [ 1 , i − 1 ] \rm [1,i-1] [1,i1]中第 k \rm k k小的数的位置,被移动的这个数不会成为第 k + 1 \rm{k+1} k+1轮排序时 [ 1 , i − 1 ] \rm[1,i-1] [1,i1]中第 k + 1 \rm{k + 1} k+1小的数,不会影响答案。

k \rm k k轮排序后倘若 a i &gt; t p \rm a_i&gt;tp ai>tp,又因为 a k ≤ t p \rm a_k\le tp aktp,此时不会发生交换。如果 a i &lt; t p \rm a_i&lt;tp ai<tp,被换到第 i \rm i i个位置的就是 t p \rm tp tp。设 [ 1 , i − 1 ] \rm[1,i-1] [1,i1]中有 j \rm j j个数比 a i \rm{a_i} ai小,那么前 j \rm j j a i \rm a_i ai的位置不会有变化,而到第 j + c ( c &gt; 0 ) \rm j + c(c&gt;0) j+cc>0轮时,当 a i \rm a_i ai被换到 a j + c \rm a_{j+c} aj+c上时, a j + c \rm a_{j+c} aj+c此时的值一定是 [ 1 , i − 1 ] \rm[1,i-1] [1,i1]中第 j + c \rm {j+c} j+c小的值,依次类推到了第 k \rm k k轮,那么被换到 a i \rm a_i ai上的就是是 [ 1 , i − 1 ] \rm[1,i-1] [1,i1]中第 k \rm k k小的值,结论得证。

因为有我们之前证明的那个小结论,我们算出 k \rm k k后对于初始序列用大根堆维护到当前位置的前 k \rm k k小的数即可,复杂度 O(n log n) \text{O(n log n)} O(n log n)

#include <bits/stdc++.h>

#define For(i, a, b) for (int i = a; i <= b; ++ i) 

using namespace std;

const int N = 1e6 + 7;

long long cnt;

int n, k, a[N], b[N];

priority_queue <int> q; 

int main() {

	cin >> n >> cnt;
	For(i, 1, n) scanf("%d", &a[i]), b[i] = a[i];
	For(i, 1, n) {
		if (cnt < n - i) break;
		cnt -= n - i, k = i;
		q.push(a[i]), b[i] = i;
	}
	if (k) For(i, k + 1, n) { 
		b[i] = max(a[i], q.top());
		q.push(a[i]), q.pop();
	}
	For(j, k + 2, n) {
		if (!(cnt --)) break;
		if (b[j] < b[k + 1]) 
			swap(b[j], b[k + 1]);
	}
	For(i, 1, n) printf("%d%c", b[i], i == n ? '\n' : ' ');

	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值