题意
传送门 P6033 [NOIP2004 提高组] 合并果子 加强版
题解
求解体力值对应了一个二叉 H u f f m a n Huffman Huffman 树问题,时间复杂度的瓶颈在于二叉堆每次增添元素的 O ( log n ) O(\log n) O(logn) 时间。假设果子重量 a i a_i ai 有序,那么每次合并的果子总重量不小于前一次果子合并的总重量,那么可以使用 2 2 2 个队列分别维护初始有序的果子与合并的果子,每次合并果子插入后者,可以保证 2 2 2 个队列都是有序的。那么每次选取最小重量的果子只用 O ( 1 ) O(1) O(1) 比较 2 2 2 个队列的队首元素。
算法复杂度的瓶颈在于初始对果子重量的排序,需要使用接近线性复杂度的排序算法。
桶排序 + 队列
桶排序的基本思想是在元素值域上按顺序分为一块块区域(桶),将各元素划分到其对应值域范围的区域,然后分别对各个区域的元素进行排序,最后合并各区域元素。设区域个数为 b b b 且元素均匀划分至各区域,排序时间复杂度 O ( b × ( n / b ) × log ( n / b ) ) O(b\times (n/b)\times \log (n/b)) O(b×(n/b)×log(n/b)),归并时间复杂度 O ( n ) O(n) O(n)。若元素值域较小,如本题的 [ 1 , 1 0 5 ] [1,10^5] [1,105],可直接划分出与值域规模相当的区域数量,此时排序总时间复杂度 O ( n ) O(n) O(n)。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 100000;
int N, C[maxn + 1];
queue<ll> X, Y;
inline void read(int &x)
{
x = 0;
char c = 0;
for (; c < '0' || c > '9'; c = getchar())
;
for (; '0' <= c && c <= '9'; c = getchar())
x = (x << 1) + (x << 3) + c - '0';
}
inline ll get()
{
ll x;
if (Y.empty() || (X.size() && X.front() < Y.front()))
x = X.front(), X.pop();
else
x = Y.front(), Y.pop();
return x;
}
int main()
{
read(N);
for (int i = 0, a; i < N; ++i)
read(a), ++C[a];
for (int i = 1; i <= maxn; ++i)
while (C[i]--)
X.push(i);
ll res = 0;
for (int i = 1; i < N; ++i)
{
ll x = get(), y = get(), s = x + y;
res += s;
Y.push(s);
}
printf("%lld\n", res);
return 0;
}
基数排序 + 队列
当元素值域较大时,特别构造的数据可以使桶排序时间复杂度退化为 O ( n log n ) O(n\log n) O(nlogn),此时使用基数排序是一种可能的更优方案。
基数排序的基本思想是将元素看做 b b b 进制数字,由于高位数字权重更大,从低位向高位处理,每次对当前这一位进行排序,由于 b b b 相对于元素值域较小,采用桶排序 O ( b ) O(b) O(b) 处理。总时间复杂度 O ( ( n + b ) log b max i = 0 n − 1 { a i } ) O((n+b)\log_{b}^{\max_{i=0}^{n-1}\{a_i\}}) O((n+b)logbmaxi=0n−1{ai})。一般取 b = 2 k b=2^k b=2k,使用位运算代替取模运算高效地求出元素 b b b 进制下某一位的值;合适地选取 b b b,可以使复杂度接近线性复杂度。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 10000005;
int N, A[maxn], B[maxn];
queue<ll> X, Y;
inline void read(int &x)
{
x = 0;
char c = 0;
for (; c < '0' || c > '9'; c = getchar())
;
for (; '0' <= c && c <= '9'; c = getchar())
x = (x << 1) + (x << 3) + c - '0';
}
void radix_sort(int *a, int n)
{
int cnt[256];
for (int i = 0; i < 17; i += 8)
{
memset(cnt, 0, sizeof(cnt));
for (int j = 0; j < n; ++j)
++cnt[a[j] >> i & 255];
for (int j = 1; j < 256; ++j)
cnt[j] += cnt[j - 1];
for (int j = n - 1; j >= 0; --j)
B[--cnt[a[j] >> i & 255]] = a[j];
memcpy(a, B, sizeof(int) * n);
}
}
inline ll get()
{
ll x;
if (Y.empty() || (X.size() && X.front() < Y.front()))
x = X.front(), X.pop();
else
x = Y.front(), Y.pop();
return x;
}
int main()
{
read(N);
for (int i = 0; i < N; ++i)
read(A[i]);
radix_sort(A, N);
for (int i = 0; i < N; ++i)
X.push(A[i]);
ll res = 0;
for (int i = 1; i < N; ++i)
{
ll x = get(), y = get(), s = x + y;
res += s;
Y.push(s);
}
printf("%lld\n", res);
return 0;
}