2020.07.25日常总结——桶排&双队列模拟优先队列

洛谷P6033   合并果子   加强版 \color{green}{\texttt{洛谷P6033 合并果子 加强版}} 洛谷P6033 合并果子 加强版

[Problem] \color{blue}{\texttt{[Problem]}} [Problem]

在这里插入图片描述

在这里插入图片描述

[Solution] \color{blue}{\texttt{[Solution]}} [Solution]

这题的弱化版相信大家一定都知道——就是洛谷的P1090。它是一道非常经典的贪心(或者说建哈弗曼树)的题目。

可是,这道题的数据范围非常的变态,以至于我们只能用 O ( n ) O(n) O(n) 的算法,连 O ( n × log ⁡ n ) O(n \times \log n) O(n×logn) 的算法都不行。

一点一点来解决,先看看原题(弱化版)中有哪些地方是 O ( n × log ⁡ n ) O(n \times \log n) O(n×logn) 的,再来想怎么样把它们变成 O ( n ) O(n) O(n)

注意,这是一个非常有用的思考方式。先考虑一个比较简单易想的弱化版,再看弱化版中哪些地方耗费了大量的时间,最后优化这些地方,使得整个算法的时间复杂度可以让我们接受。

第一个地方就是排序。我们发现这道题有一个非常好的性质: 1 ≤ a i ≤ 1 × 1 0 5 ( 1 ≤ i ≤ n ) 1 \leq a_i \leq 1 \times 10^5(1 \leq i \leq n) 1ai1×105(1in)

这有什么用呢?我们可以用桶排来代替快排。所谓的桶排,就是开一个桶 cnt \texttt{cnt} cnt,其中 cnt i \texttt{cnt}_{i} cnti 表示数字 i i i 的出现次数。排序变得非常简单:

for(int i=1;i<=100000;i++)
	for(int j=1;j<=cnt[i];j++)
		b[++tot]=i;//b数组即排序后数组

第二个地方就是贪心的过程。我们需要用优先队列,而优先队列是 O ( n × log ⁡ n ) O(n \times \log n) O(n×logn) 的。怎么办办呢?两个方法。

  1. 换一个数据结构。但这个对于本题来说有点难,至少笔者不会。
  2. 用其它数据结构代替它。

用什么数据结构来代替它呢?两个队列!

队列 Q 1 Q_1 Q1 初始化为排好序后的 b b b 数组, Q 2 Q_2 Q2 初始化为空。有什么用呢?

首先要取出最小的两个数。这个简单,每次取一个数只需要从 Q 1 Q_1 Q1 Q 2 Q_2 Q2 的对头(它们是这个队列中最小的元素),看看谁更小,谁小要谁。

然后是插入元素。我们把它直接放到 Q 2 Q_2 Q2 的尾部。

就这么简单。但是它为什么是对的呢? Q 1 Q_1 Q1 就说是我们人为的排好了序,是有序的, Q 2 Q_2 Q2 也一定是有序的吗?

答案是肯定的,大家可以想想为什么(也可以去搜索一下,真的很简单,这里就不证明了)。

于是我们可以在 O ( n ) O(n) O(n) 的时间复杂度内解决这道题了。

[code] \color{blue}{\texttt{[code]}} [code]

const int N=1e7+100;
const int M=1e5+100;
long long ans;int n;
int a[N],cnt[M];//桶 
queue<long long> q1,q2;
inline long long get_first(){
	if (q2.empty()||(!q1.empty()&&q1.front()<q2.front()))
		{long long x=q1.front();q1.pop();return x;}
	else{long long x=q2.front();q2.pop();return x;}
}
int main(){
	memset(cnt,0,sizeof(cnt));
	n=read();ans=0ll;//init
	for(int i=1;i<=n;i++)
		cnt[a[i]=read()]++;
	for(int i=1;i<=100000;i++)
		for(int j=1;j<=cnt[i];j++)
			q1.push(i);//O(N)的桶排 
	for(register int i=1;i<n;i++){
		long long x=get_first();
		long long y=get_first();
		ans+=x+y;q2.push(x+y);
	}
	printf("%lld",ans);
	return 0;
}

read() 函数就是快读函数,这里不给出了。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值