堆 题目集合
T1 合并果子
题目描述
一个果园里,多多已经将所有的果子打了下来,而且按果子的不同种类分成了不同的堆。多多决定把所有的果子合成一堆。
每一次合并,多多可以把两堆果子合并到一起,消耗的体力等于两堆果子的重量之和。可以看出,所有的果子经过n-1次合并之后,就只剩下一堆了。多多在合并果子时总共消耗的体力等于每次合并所耗体力之和。 因为还要花大力气把这些果子搬回家,所以多多在合并果子时要尽可能地节省体力。假定每个果子重量都为1,并且已知果子的种类数和每种果子的数目,你的任务是设计出合并的次序方案,使多多耗费的体力最少,并输出这个最小的体力耗费值。
例如有3种果子,数目依次为1,2,9。可以先将1、2堆合并,新堆数目为3,耗费体力为3。接着,将新堆与原先的第三堆合并,又得到新的堆,数目为12,耗费体力为12。所以多多总共耗费体力=3+12=15。可以证明15为最小的体力耗费值。
-
输入格式
输入包括两行。第一行是一个整数n(1<=n<=10000),表示果子的种类数。
第二行包含n个整数,用空格分隔,第i个整数ai(1<=ai<=20000)是第i种果子的数目。
-
输出格式
输出包括一行,这一行只包含一个整数,也就是最小的体力耗费值。输入数据保证这个值小于2的31次方。
样例输入
3
1 2 9
样例输出
15
数据范围与提示
对于30%的数据,保证有n<=1000:
对于50%的数据,保证有n<=5000;
对于全部的数据,保证有n<=10000。
AC代码
// 这道题。。。
// 没做对的来个对照疗法叭(GM讲过)
#include <cstdio>
#include <algorithm>
using namespace std;
const int MAXN = 10005;
int h[MAXN], h_size, a[MAXN];
// 小根堆
int Put_Heap(int k) {
h[++h_size] = k;
int now = h_size, fa;
while(now > 1) {
fa = now >> 1;
if(h[now] >= h[fa]) break;
swap(h[now], h[fa]);
now = fa;
}
return 0;
}
int Get_Heap() {
int ret = h[1];
h[1] = h[h_size];
int now = 1;
h_size--;
int son;
while(now <= h_size / 2) {
son = now * 2;
if(now < h_size && h[son + 1] < h[son])
son++;
if(h[now] <= h[son]) break;
swap(h[now], h[son]);
now = son;
}
return ret;
}
int main() {
int n, x, y, ans = 0;
scanf("%d", &n);
for(int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
Put_Heap(a[i]);
}
while(h_size != 1) {
x = h[1];
Get_Heap();
y = h[1];
Get_Heap();
ans = ans + x + y;
Put_Heap(x + y);
}
printf("%d\n", ans);
return 0;
}
T2 最小函数值
题目描述
有n个函数,分别为F1,F2,...,Fn。定义Fi(x)=A i x 2 +Bix+Ci(x∈N∗,正整数集合)。给定这些Ai、Bi和Ci,请求出所有函数的所有函数值中最小的m个(如有重复的要输出多个)。
-
输入格式
第一行输入两个正整数n和m。以下n行每行三个正整数,其中第i行的三个数分别位Ai、Bi和Ci。输入数据保证Ai≤10,Bi≤100,Ci≤10000。
-
输出格式
将这n个函数所有可以生成的函数值排序后的前m个元素。这m个数应该输出到一行,用空格隔开。
样例输入
3 10
4 5 3
3 4 5
1 7 1
样例输出
9 12 12 19 25 29 31 44 45 54
数据范围与提示
n,m≤10000。
AC代码
#include <cstdio>
#include <algorithm>
using namespace std;
const int MAXN = 1000005;
struct edge {
int index, x, v;
// 在堆里存当前的值是由第几个函数得出的,此函数中现在x的值
// 以及函数值
} h[MAXN];
int h_size;
struct node {
int a, b, c;
} s[MAXN];
int Put_Heap(edge k);
edge Get_Heap();
int main() {
int n, m;
scanf ("%d %d", &n, &m);
for(int i = 1; i <= n; i++) {
scanf ("%d %d %d", &s[i].a, &s[i].b, &s[i].c);
edge t;
t.v = s[i].a + s[i].b + s[i].c; // 每一个函数一定是在x=1时取最小
t.index = i;
t.x = 1;
Put_Heap(t); // 把最小先放进堆
}
while(m != 0) {
edge now = Get_Heap(); // 拿出堆的第一个,及最小值
printf("%d ", now.v);
now.x = now.x + 1;
int i = now.index;
now.v = s[i].a * now.x * now.x + s[i].b * now.x + s[i].c;
// 将最小值的x加一后计算,再次放进堆
Put_Heap(now);
m--;
}
return 0;
}
// 小根堆
int Put_Heap(edge k) {
h[++h_size] = k;
int now = h_size, fa;
while(now > 1) {
fa = now >> 1;
if(h[now].v >= h[fa].v) break;
swap(h[now], h[fa]);
now = fa;
}
return 0;
}
edge Get_Heap() {
edge ret;
ret = h[1];
h[1] = h[h_size];
int now = 1;
h_size--;
int son;
while(now <= h_size / 2) {
son = now * 2;
if(now < h_size && h[son + 1].v < h[son].v)
son++;
if(h[now].v <= h[son].v) break;
swap(h[now], h[son]);
now = son;
}
return ret;
}
T3 POJ2442 Sequence
题目描述
Given m sequences, each contains n non-negative integer. Now we may select one number from each sequence to form a sequence with m integers. It's clear that we may get n ^ m this kind of sequences. Then we can calculate the sum of numbers in each sequence, and get n ^ m values. What we need is the smallest n sums. Could you help us?
题目大意:
给定M个长度为N的序列,从每个序列中任意取一个数求和,可以构成N的M次方个和,求其中最小的N个和。
-
输入格式
The first line is an integer T, which shows the number of test cases, and then T test cases follow. The first line of each case contains two integers m, n (0 < m <= 100, 0 < n <= 2000). The following m lines indicate the m sequence respectively. No integer in the sequence is greater than 10000. -
输出格式
For each test case, print a line with the smallest n sums in increasing order, which is separated by a space.
样例输入
1
2 3
1 2 3
2 2 3
样例输出
3 3 4
分析
【来自课件】
1、首先考虑M=2时的简化问题,即从2个序列中任取一个数相加构成的N2个和中求出前N小的和,设这两个序列为A和B,把它们分别排序;
2、由于从小到大排过序,可以发现,最小和一定是A[1]+B[1],次小和是min(A[1]+B[2],A[2]+B[1]),假设次小和是A[2]+B[1],那么第三小和就在第二小和剩余的部分也就是A[1]+B[2]加上A[2+1]+B[1]和A[2]+B[1+1]中比较一个min,可以推广成,如果A[i]+B[j]是第k小和,那么第k+1小和就在第k小和剩余的部分加上A[i+1]+B[j]和A[i]+B[j+1]两个和里去找一个min
需要注意的是,A[1]+B[2]和A[2]+B[1]都会产生A[2]+B[2],那么集合中很可能出现两个A[2]+B[2],答案会出现错误!
解决此问题:堆中每个结点设置成为(i, j, last)三元组,其中last是一个bool变量,如果last为true,后续加入堆中的结点只能是(i, j+1, true),如果last为false,后续加入堆中的结点为两个(i+1, j, false)和(i, j+1, true),一开始,堆中只有(1, 1, false)
AC代码
// GM也讲过。。。
// 但笔者想偷懒所以并没有用GM的三元组方法,所以建议跳过
#include <cstdio>
#include <queue>
#include <algorithm>
using namespace std;
const int MAXN = 10005;
int a[MAXN], b[MAXN];
priority_queue <int> q;
int main() {
int t;
scanf ("%d", &t);
while(t--) {
int n, m;
scanf ("%d %d", &n, &m);
for(int i = 1; i <= m; i++)
scanf ("%d", &a[i]);
sort(a + 1, a + m + 1);
n--;
while(n--) {
for(int i = 1; i <= m; i++)
scanf ("%d", &b[i]);
sort(b + 1, b + m + 1);
for(int i = 1; i <= m; i++) {
q.push(a[1] + b[i]);
}
for(int i = 2; i <= m; i++)
for(int j = 1; j <= m; j++) {
int s = a[i] + b[j];
if(s < q.top()) {
q.pop();
q.push(s);
}
}
for(int i = 1; i <= m; i++) {
a[i] = q.top();
q.pop();
}
sort(a + 1, a + m + 1);
}
for(int i = 1; i < m; i++)
printf("%d ", a[i]);
printf("%d\n", a[m]);
}
return 0;
}
T4 POJ1456 Supermarket
题目描述
A supermarket has a set Prod of products on sale. It earns a profit px for each product x∈Prod sold by a deadline dx that is measured as an integral number of time units starting from the moment the sale begins. Each product takes precisely one unit of time for being sold. A selling schedule is an ordered subset of products Sell ≤ Prod such that the selling of each product x∈Sell, according to the ordering of Sell, completes before the deadline dx or just when dx expires. The profit of the selling schedule is Profit(Sell)=Σx∈Sellpx. An optimal selling schedule is a schedule with a maximum profit.
For example, consider the products Prod={a,b,c,d} with (pa,da)=(50,2), (pb,db)=(10,1), (pc,dc)=(20,2), and (pd,dd)=(30,1). The possible selling schedules are listed in table 1. For instance, the schedule Sell={d,a} shows that the selling of product d starts at time 0 and ends at time 1, while the selling of product a starts at time 1 and ends at time 2. Each of these products is sold by its deadline. Sell is the optimal schedule and its profit is 80.
Write a program that reads sets of products from an input text file and computes the profit of an optimal selling schedule for each set of products.
题目大意:
给定N个商品,每个商品的利润pi和过期时间di,每天只能卖一个商品,过期商品不能再卖,求如何安排每天卖的商品,可使得收益最大?
-
输入格式
A set of products starts with an integer 0 <= n <= 10000, which is the number of products in the set, and continues with n pairs pi di of integers, 1 <= pi <= 10000 and 1 <= di <= 10000, that designate the profit and the selling deadline of the i-th product. White spaces can occur freely in input. Input data terminate with an end of file and are guaranteed correct. -
输出格式
For each set of products, the program prints on the standard output the profit of an optimal selling schedule for the set. Each result is printed from the beginning of a separate line.
样例输入
4 50 2 10 1 20 2 30 1
7 20 1 2 1 10 3 100 2 8 2 5 20 50 10
样例输出
80
185
数据范围与提示
The sample input contains two product sets. The first set encodes the products from table 1. The second set is for 7 products. The profit of an optimal schedule for these products is 185.
分析
贪心。。。
对于每一天 \(t\),在保证不卖出过期商品的情况下,尽量卖出利润前 \(t\) 大的
AC代码
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int MAXN = 10005;
int h[MAXN], h_size;
struct node {
int v, d;
} s[MAXN];
bool cmp(node x, node y) {
return x.d < y.d;
}
// 小根堆
int Put_Heap(int k) {
h[++h_size] = k;
int now = h_size, fa;
while(now > 1) {
fa = now >> 1;
if(h[now] >= h[fa]) break;
swap(h[now], h[fa]);
now = fa;
}
return 0;
}
int Get_Heap() {
int ret = h[1];
h[1] = h[h_size];
int now = 1;
h_size--;
int son;
while(now <= h_size / 2) {
son = now * 2;
if(now < h_size && h[son + 1] < h[son])
son++;
if(h[now] <= h[son]) break;
swap(h[now], h[son]);
now = son;
}
return ret;
}
int main() {
int n;
while(scanf ("%d", &n)) {
if(n == 0) {
printf("0\n");
continue;
}
memset(h, 0, sizeof h);
h_size = 0;
for(int i = 1; i <= n; i++)
scanf ("%d %d", &s[i].v, &s[i].d);
sort(s + 1, s + n + 1, cmp);
for(int i = 1; i <= n; i++) {
if(s[i].d == h_size && s[i].v > h[1]) {
// 这种情况说明前t天已经安排了t件商品卖出
// 且当前即将过期的商品的利润比前t天利润最小的商品的利润大
// 所以用这件商品替换
Get_Heap();
Put_Heap(s[i].v);
}
else if(s[i].d > h_size) Put_Heap(s[i].v);
// 还没过期,且前面有空位?直接进入堆
}
int ans = 0;
while(h_size != 0) ans += Get_Heap();
// 最后留在堆里的就是答案,累加答案
printf("%d\n", ans);
}
return 0;
}
T5 鱼塘钓鱼
题目描述
有N个鱼塘排成一排(N<100),每个鱼塘中有一定数量的鱼,例如:N=5时,如下表:
鱼塘编号 1 2 3 4 5
每1分钟能钓到的鱼的数量(1..1000) 10 14 20 16 9
每1分钟能钓鱼数的减少量(1..100) 2 4 6 5 3
当前鱼塘到下一个相邻鱼塘需要的时间(单位:分钟)3 5 4 4
即:在第1个鱼塘中钓鱼第1分钟内可钓到10条鱼,第2分钟内只能钓到8条鱼,……,第5分钟以后再也钓不到鱼了。从第1个鱼塘到第2个鱼塘需要3分钟,从第2个鱼塘到第3个鱼塘需要5分钟,……
给出一个截止时间T(T<1000),设计一个钓鱼方案,从第1个鱼塘出发,希望能钓到最多的鱼。
假设能钓到鱼的数量仅和已钓鱼的次数有关,且每次钓鱼的时间都是整数分钟。
-
输入格式
共5行,分别表示:第1行为N;
第2行为第1分钟各个鱼塘能钓到的鱼的数量,每个数据之间用一空格隔开;
第3行为每过1分钟各个鱼塘钓鱼数的减少量,每个数据之间用一空格隔开;
第4行为当前鱼塘到下一个相邻鱼塘需要的时间;
第5行为截止时间T。
-
输出格式
一个整数(不超过2^31−1),表示你的方案能钓到的最多的鱼。
样例输入
5
10 14 20 16 9
2 4 6 5 3
3 5 4 4
14
样例输出
76
分析
贪心。。。枚举最后在什么地方停下的
显然只需要找到最优解在各个鱼塘钓了几次
AC代码
#include <cstdio>
#include <algorithm>
using namespace std;
const int MAXN = 10005;
int road[MAXN], h_size;
struct edge {
int m, k; // a[i]池塘的初始鱼量m,以及每一次减少的量k
} a[MAXN];
struct node {
int x, index;
// 目前a[index]池塘还剩余的鱼量
} h[MAXN];
// 大根堆
int Put_Heap(node k) {
h[++h_size] = k;
int now = h_size, fa;
while(now > 1) {
fa = now >> 1;
if(h[now].x <= h[fa].x) break;
swap(h[now], h[fa]);
now = fa;
}
return 0;
}
node Get_Heap() {
node ret = h[1];
h[1] = h[h_size];
int now = 1;
h_size--;
int son;
while(now <= h_size / 2) {
son = now * 2;
if(now < h_size && h[son + 1].x > h[son].x)
son++;
if(h[now].x >= h[son].x) break;
swap(h[now], h[son]);
now = son;
}
return ret;
}
int main() {
int n, t;
scanf ("%d", &n);
for(int i = 1; i <= n; i++) scanf ("%d", &a[i].m);
for(int i = 1; i <= n; i++) scanf ("%d", &a[i].k);
for(int i = 1; i < n; i++) scanf ("%d", &road[i]); // 输入池塘间的距离
scanf ("%d", &t);
int st = 0, ma = 0;
for(int i = 1; i <= n; i++) {
int nt = t - st, ans = 0; // 计算剩余时间,并把答案置为零
h_size = 0;
for(int j = 1; j <= i; j++) { // 收集池塘的资料
node h_;
h_.index = j;
h_.x = a[j].m;
Put_Heap(h_);
}
while(nt > 0 && h[1].x > 0) { // 贪心选取鱼最多的池塘
node tot = Get_Heap();
ans += tot.x;
tot.x -= a[tot.index].k; // 修改剩余鱼量
Put_Heap(tot);
nt--;
}
ma = max(ma, ans); // 刷新最优解
st += road[i]; // 累计走路需要的时间
while(h_size > 0) Get_Heap();
}
printf("%d\n", ma);
return 0;
}
所有登峰造极之人最终都会抵达同一个终点,哪怕时代变迁,哪怕所经历的路途千差万别,他们最终都会抵达同样的终点。