【BHOJ 女娲加农炮 |、||】贪心 | 优先队列 | 堆 | E

这次我们通过两道例题来总结一下优先队列的用法和实现:

 

目录:

【BHOJ 1512】女娲加农炮

【BHOJ 1517】女娲加农炮II

 


 

【BHOJ 1512】女娲加农炮

核心:贪心 + 优先队列

URL:【BHOJ 1512】女娲加农炮

时间限制: 3000 ms 内存限制: 131072 kb

总通过人数: 96 总提交人数: 172

 

题目简述

已知有N种不同的原子核,第i种原子核的重量为a[i]。

将两种原子核聚合在一起,消耗的能量等于两种原子核的重量之和。在n-1次聚合之后,聚合完成。求出最小能量消耗值。

输入

多组数据输入,第一个数为原子核的种类N。(N <= 1e6)

接下来N个整数,代表N种原子核的重量。(在int范围内并用空格隔开)

输出

对于每组数据,输出一行,为聚合过程能量消耗的最小值。(保证结果在int范围内)

输入样例

4
1 2 3 4

输出样例

19

 

分析 

这道题是典型的贪心 + 优先队列,主要的思路是每次都选当前待聚合原子核中最轻的两个来聚合,直到聚合得到一个唯一的原子核,期间累加能量消耗,最后输出即可。

还有一个要注意的小细节就是求和的时候要用 long long。

 

代码

首先是 STL 版:

#include <iostream>
#include <vector>
#include <queue>

#define PC putchar
#define LL long long
#define sc(x) {register char _c=getchar(),_v=1;for(x=0;_c<48||_c>57;_c=getchar())if(_c==45)_v=-1;for(;_c>=48&&_c<=57;x=(x<<1)+(x<<3)+_c-48,_c=getchar());x*=_v;}
#define se(x) {register char _c=getchar(),_v=1;for(x=0;_c<48||_c>57;_c=getchar())if(_c==45)_v=-1;else if(_c==-1)return 0;for(;_c>=48&&_c<=57;x=(x<<1)+(x<<3)+_c-48,_c=getchar());x*=_v;}
void PRT(const LL a){if(a>=10)PRT(a/10);putchar(a%10+48);}

std::priority_queue<LL, std::vector<LL>, std::greater<LL>> pq;
int main()
{
	int n;
	while (1)
	{
		se(n)
		LL ans = 0, sum;
		while (n--)
		{
			int tp;
			sc(tp)
			pq.push(tp);
		}
		while (pq.size() >= 2)     // 至少还有两个,仍然需要继续聚合
		{
			sum = pq.top();    // 每次拿两个最小的出来
			pq.pop();
			sum += pq.top();
			pq.pop();
			ans += sum;
			pq.push(sum);
		}
		PRT(ans), PC(10);
		while (pq.size())    // 清空优先队列
			pq.pop();
	}
}

 

然后是通过最小堆实现的自定义优先队列:

为了提高效率:

  1. 建立了哨兵,节省了进行上浮操作时对下标进行判断的时间(push的时候,如果加入的元素比当前堆内所有有效元素都小,那么就会自动在哨兵处停下,不必担心不判断下标而陷入死循环)
  2. 使用数组一次性开辟足够的空间而不是多次动态申请空间
  3. 使用了宏函数而不是通用函数去定义下沉操作函数(消除频繁调用函数的开销)
  4. 当使用C++原生类型的时候可以不使用引用类型以加快速度(用 typedef会报错构造函数,只好用#define)

具体代码:

#include <iostream>

#define PC putchar
#define LL long long
#define sc(x) {register char _c=getchar(),_v=1;for(x=0;_c<48||_c>57;_c=getchar())if(_c==45)_v=-1;for(;_c>=48&&_c<=57;x=(x<<1)+(x<<3)+_c-48,_c=getchar());x*=_v;}
#define se(x) {register char _c=getchar(),_v=1;for(x=0;_c<48||_c>57;_c=getchar())if(_c==45)_v=-1;else if(_c==-1)return 0;for(;_c>=48&&_c<=57;x=(x<<1)+(x<<3)+_c-48,_c=getchar());x*=_v;}
void PRT(const LL a){if(a>=10)PRT(a/10);putchar(a%10+48);}

template <class Type, int MN>	// 小顶堆 
class KPQ
{

#define ChosenType Type 		// 用原生类型还是自定义类
#define CMP <				// >就变成大顶堆 

#define perc_down(root) int p = root; \
						for (int c, E=cnt>>1; p<=E; p=c) \
						{ \
							c = p<<1; \
							if (c!=cnt && heap[c+1] CMP heap[c]) \
								c++; \
							if (heap[c] CMP tp) \
								heap[p] = heap[c]; \
							else break; \
						} \
						heap[p] = tp
	public:

		Type heap[MN];
		int cnt;

	public:

		KPQ(const ChosenType MIN_SENTRY) : cnt(0)
		{
			*heap = MIN_SENTRY;
		}

		void push(const ChosenType data)
		{
			register int i = ++cnt;
			for (; data CMP heap[i>>1]; i>>=1)
				heap[i] = heap[i>>1];
			heap[i] = data;
		}

		void pop(void)
		{
			const ChosenType tp = heap[cnt--];
			perc_down(1);
		}

		inline const ChosenType top(void)
		{
			return heap[1];
		}

		inline bool empty(void)
		{
			return !cnt;
		}

		inline int size(void)
		{
			return cnt;
		}

		inline void clear(void)
		{
			cnt = 0;
		}

		void build(int n)
		{
			cnt = n;
			// for i:[1,n] scan
			for (int i=1; i<=n; i++)
				sc(heap[i])
			for (int i=n>>1; i>0; i--)
			{
				Type tp = heap[i];
				perc_down(i);
			}
		}

		void erase(const ChosenType data)
		{
			for (int i=1; i<=cnt; i++)
			{
				if (heap[i] == data)
				{
					heap[i] = heap[cnt--];
					const ChosenType tp = heap[cnt+1];
					perc_down(i);
					return;
				}
			}
		}
};

KPQ<LL, 1000007> pq(-1);
int main()
{
	int n;
	while (1)
	{
		se(n)
		LL ans = 0, sum;
		pq.build(n);
		while (pq.size() >= 2)     // 至少还有两个,仍然需要继续聚合
		{
			sum = pq.top();    // 每次拿两个最小的出来
			pq.pop();
			sum += pq.top();
			pq.pop();
			ans += sum;
			pq.push(sum);
		}
		PRT(ans), PC(10);
		pq.clear();          // 清空优先队列
	}
}

 


 

【BHOJ 1517】女娲加农炮II

核心:贪心 + 优先队列

URL:【BHOJ 1517】女娲加农炮 II

时间限制: 1000 ms 内存限制: 65536 kb

总通过人数: 70 总提交人数: 77

 

题目简述

已知有N种不同的原子核(这里请忽略自然界原子核的种类上限),第i种原子核的重量为a[i]。

将三种原子核聚合在一起,消耗的能量等于三种原子核的重量之和。在经过多次聚合之后,聚合完成。(如果原子核的数量小于3则不进行进一步的聚合)芸如想使整个聚合过程消耗的能量最小,请求出这个最小的能量值。

输入

多组数据输入,第一个数为原子核的种类N。(N <= 1e6)

接下来N个整数,代表N种原子核的重量。(在int范围内并用空格隔开)

输出

对于每组数据,输出一行,为聚合过程能量消耗的最小值。(保证结果在int范围内)

输入样例

4
1 2 3 4

输出样例

6

分析

这道题女娲加农炮 I 的思路和解题方法都是一样的,也是朴素的 贪心 + 优先队列,不过是每次合并三个原子核。

代码 

#include <iostream>
#include <algorithm>
#define PC putchar
#define LL long long
#define sc(x) {register char _c=getchar(),_v=1;for(x=0;_c<48||_c>57;_c=getchar())if(_c==45)_v=-1;for(;_c>=48&&_c<=57;x=(x<<1)+(x<<3)+_c-48,_c=getchar());x*=_v;}
#define se(x) {register char _c=getchar(),_v=1;for(x=0;_c<48||_c>57;_c=getchar())if(_c==45)_v=-1;else if(_c==-1)return 0;for(;_c>=48&&_c<=57;x=(x<<1)+(x<<3)+_c-48,_c=getchar());x*=_v;}
void PRT(const LL a){if(a>=10)PRT(a/10);putchar(a%10+48);}

template <class Type, int MN>	// 小顶堆 
class KPQ
{

#define ChosenType Type
#define CMP <					// >就变成大顶堆

#define perc_down(root) int p = root; \
						for (int c, E=cnt>>1; p<=E; p=c) \
						{ \
							c = p<<1; \
							if (c!=cnt && heap[c+1] CMP heap[c]) \
								c++; \
							if (heap[c] CMP tp) \
								heap[p] = heap[c]; \
							else break; \
						} \
						heap[p] = tp
	public:

		Type heap[MN];
		int cnt;

	public:

		KPQ(const ChosenType MIN_SENTRY) : cnt(0)
		{
			*heap = MIN_SENTRY;
		}

		void push(const ChosenType data)
		{
			register int i = ++cnt;
			for (; data CMP heap[i>>1]; i>>=1)
				heap[i] = heap[i>>1];
			heap[i] = data;
		}

		void pop(void)
		{
			const ChosenType tp = heap[cnt--];
			perc_down(1);
		}

		inline const ChosenType top(void)
		{
			return heap[1];
		}

		inline bool empty(void)
		{
			return !cnt;
		}

		inline int size(void)
		{
			return cnt;
		}

		inline void clear(void)
		{
			cnt = 0;
		}

		void build(int n)
		{
			cnt = n;
			// for i:[1,n] scan
			for (int i=1; i<=n; i++)
				sc(heap[i])
			for (int i=n>>1; i>0; i--)
			{
				const Type tp = heap[i];
				perc_down(i);
			}
		}

		void erase(const ChosenType data)
		{
			for (int i=1; i<=cnt; i++)
			{
				if (heap[i] == data)
				{
					heap[i] = heap[cnt--];
					const ChosenType tp = heap[cnt+1];
					perc_down(i);
					return;
				}
			}
		}
};

KPQ<LL, 1000007> pq(-1);


int main()
{
	int n;
	while (1)
	{
		se(n)
		LL ans = 0, sum;
		pq.build(n);
		while (pq.size() >= 3)     // 至少还有三个,仍然需要继续聚合
		{
			sum = pq.top();    // 每次拿三个最小的出来
			pq.pop();
			sum += pq.top();
			pq.pop();
			sum += pq.top();
			pq.pop();
			ans += sum;
			pq.push(sum);
		}
		PRT(ans), PC(10);
		pq.clear();          // 清空优先队列
	}
}

 

当然优先队列也可以通过平衡树实现,不过用堆实现的优先队列已经有比较好的性能了,从实际角度考虑还是选用堆。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值