题目地址:
https://www.luogu.com.cn/problem/P6033
题目背景:
本题除【数据范围】外与P1090完全一致。
题目描述:
在一个果园里,多多已经将所有的果子打了下来,而且按果子的不同种类分成了不同的堆。多多决定把所有的果子合成一堆。每一次合并,多多可以把两堆果子合并到一起,消耗的体力等于两堆果子的重量之和。可以看出,所有的果子经过
(
n
−
1
)
(n - 1)
(n−1)次合并之后, 就只剩下一堆了。多多在合并果子时总共消耗的体力等于每次合并所耗体力之和。因为还要花大力气把这些果子搬回家,所以多多在合并果子时要尽可能地节省体力。假定每个果子重量都为
1
1
1,并且已知果子的种类数和每种果子的数目,你的任务是设计出合并的次序方案,使多多耗费的体力最少,并输出这个最小的体力耗费值。例如有
3
3
3堆果子,数目依次为
1
,
2
,
9
1,~2,~9
1, 2, 9。可以先将
1
1
1、
2
2
2堆合并,新堆数目为
3
3
3,耗费体力为
3
3
3。接着,将新堆与原先的第三堆合并,又得到新的堆,数目为
12
12
12,耗费体力为
12
12
12。所以多多总共耗费体力为
3
+
12
=
15
3+12=15
3+12=15。可以证明
15
15
15为最小的体力耗费值。
输入格式:
输入的第一行是一个整数
n
n
n,代表果子的堆数。
输入的第二行有
n
n
n个用空格隔开的整数,第
i
i
i个整数代表第
i
i
i堆果子的个数
a
i
a_i
ai。
输出格式:
输出一行一个整数,表示最小耗费的体力值。
数据范围:
本题采用多测试点捆绑测试,共有四个子任务。
Subtask 1(10 points):
1
≤
n
≤
8
1 \leq n \leq 8
1≤n≤8。
Subtask 2(20 points):
1
≤
n
≤
1
0
3
1 \leq n \leq 10^3
1≤n≤103。
Subtask 3(30 points):
1
≤
n
≤
1
0
5
1 \leq n \leq 10^5
1≤n≤105。
Subtask 4(40 points):
1
≤
n
≤
1
0
7
1 \leq n \leq 10^7
1≤n≤107。
对于全部的测试点,保证
1
≤
a
i
≤
1
0
5
1 \leq a_i \leq 10^5
1≤ai≤105。
提示:
请注意常数因子对程序效率造成的影响。
请使用类型合适的变量来存储本题的结果。
本题输入规模较大,请注意数据读入对程序效率造成的影响。
数据范围太大,需要用long储存数据,并且需要快读。同时,用最小堆来做时间是
O
(
n
log
n
)
O(n\log n)
O(nlogn)(做法参考https://blog.csdn.net/qq_46105170/article/details/113750503),可以优化到
O
(
n
)
O(n)
O(n)。其实每次要做的操作就是取最小值,这可以用双队列来做,保证两个队列各自都是单调上升的即可。这一点可以这样实现:开两个队列,第一个队列是
a
i
a_i
ai的上升序列,第二个队列一开始为空,每次直接假定两个队列各自单调上升,那么每次取最小值其实就是取队头,取两个最小值加起来直接push到第二个队列后面。稍微证明一下这个的正确性,设当前第一个队列开头的数是
x
1
≤
x
2
≤
x
3
≤
x
4
x_1\le x_2\le x_3\le x_4
x1≤x2≤x3≤x4,第二个队列开头的数是
y
1
≤
y
2
≤
y
3
≤
y
4
y_1\le y_2\le y_3\le y_4
y1≤y2≤y3≤y4,分三种情况:
1、
x
1
+
x
2
x_1+x_2
x1+x2被push到队列二后面,那么下次push的时候因为
min
{
x
3
,
y
1
}
≥
x
2
\min\{x_3,y_1\}\ge x_2
min{x3,y1}≥x2,所以再push的数一定大于等于
x
1
+
x
2
x_1+x_2
x1+x2,成立;
2、
y
1
+
y
2
y_1+y_2
y1+y2被push到队列二后面,理由同上;
3、
x
1
+
y
1
x_1+y_1
x1+y1被push到队列二后面,下次如果push的是
y
2
+
y
3
y_2+y_3
y2+y3,则
y
2
≥
y
1
,
y
3
≥
x
2
≥
x
1
y_2\ge y_1, y_3\ge x_2\ge x_1
y2≥y1,y3≥x2≥x1,成立;如果push的是
x
2
+
x
3
x_2+x_3
x2+x3理由一样,剩下的情况显然。
代码如下:
#include <iostream>
using namespace std;
const int M = 1e5 + 10, N = 1e7 + 10;
int n, a[M];
long res;
long q1[N], q2[N];
int hh1, tt1, hh2, tt2;
// 快读
void read(int &x) {
int si = 1;
x = 0;
char c = getchar();
if (c == '-') si = -1, c = getchar();
for (; '0' <= c && c <= '9'; c = getchar())
x = x * 10 + c - '0';
x *= si;
}
// 两个队列里取队头最小值
long find_min() {
long x;
if (hh2 == tt2 || hh1 < tt1 && q1[hh1] < q2[hh2]) x = q1[hh1++];
else x = q2[hh2++];
return x;
}
int main() {
read(n);
for (int i = 1, x; i <= n; i++) {
read(x);
a[x]++;
}
for (int i = 1; i < M; i++) while (a[i]) a[i]--, q1[tt1++] = i;
for (int i = 1; i < n; i++) {
long x = find_min(), y = find_min();
res += x + y;
q2[tt2++] = x + y;
}
printf("%ld\n", res);
}
时空复杂度 O ( n ) O(n) O(n)。