定义:
哈夫曼树是带权路径长度最短的树,权值较大的结点离根较近。
K叉哈夫曼树:
哈夫曼树的构造方法是贪心的每次选最小的几个节点构造。
当
K
>
2
K>2
K>2的时候需要预处理一下,因为可能最后一步合并操作的点数不到K个节点,这样的话就不是最优的了。
K
>
2
K>2
K>2预处理方法:
当
r
e
m
a
i
n
=
(
n
−
1
)
m
o
d
(
k
−
1
)
≠
0
remain=(n-1)~mod~(k-1)\neq 0
remain=(n−1) mod (k−1)=0时:
- 加入 k − 1 − r e m a i n k-1-remain k−1−remain个权重为0的虚拟节点。
- 将 r a m a i n + 1 ramain+1 ramain+1个节点先合并为1个节点。(有可能只有 r e m a i n remain remain个节点)
构造哈夫曼树:
每次取最小的
K
K
K个数合并为一个节点。
这里很容易想到用优先队列去写,复杂度
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)。
如果数组是有序的,我们可以用两个队列去优化一下,将原先的节点放入
q
1
q1
q1,合并之后的节点放入
q
2
q2
q2,这样仍然满足单调性,只需要每次取数的时候取两个队列中小的那个即可,复杂度
O
(
n
)
O(n)
O(n)
例题:NC7528D 合并序列
Code:
#include<bits/stdc++.h>
#define fi first
#define se second
#define mp make_pair
using namespace std;
typedef long long ll;
typedef pair<int,int>P;
const double eps = 1e-8;
const int NINF = 0xc0c0c0c0;
const int INF = 0x3f3f3f3f;
const ll mod = 1e9 + 7;
const ll N = 1e5 + 5;
ll n,m,a[N];
bool check(int k){
queue<ll> q1,q2;
if((n-1)%(k-1)) for(int i=1;i<=k-1-(n-1)%(k-1);i++) q1.push(0);
for(int i=1;i<=n;i++) q1.push(a[i]);
ll sum=0;
while(true){
ll tmp=0;
for(int i=1;i<=k;i++){
if(q1.empty() && q2.empty()) break;
int a1,a2;
if(q1.empty()){
tmp+=q2.front();
q2.pop();
}
else if(q2.empty()){
tmp+=q1.front();
q1.pop();
}
else{
a1=q1.front();
a2=q2.front();
if(a1<a2) {tmp+=a1;q1.pop();}
else {tmp+=a2;q2.pop();}
}
}
sum+=tmp;
if(q1.empty() && q2.empty()) break;
q2.push(tmp);
}
return sum<=m;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>a[i];
sort(a+1,a+1+n);
ll L=2,R=n+1,mid,ans=n;
while(L<R){
mid=(L+R)/2;
if(check(mid))
R=mid,ans=min(ans,mid);
else
L=mid+1;
}
cout<<ans<<'\n';
return 0;
}
Code2
#include <bits/stdc++.h>
#define fi first
#define se second
#define mp make_pair
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const double eps = 1e-8;
const int NINF = 0xc0c0c0c0;
const int INF = 0x3f3f3f3f;
const ll mod = 1e9 + 7;
const ll N = 1e6 + 5;
int n,m,a[N];
inline bool check(int k){
queue<ll> q1,q2;
ll sum=0;
int rem=(n-1)%(k-1);
if(rem){
for(int i=1;i<=min(rem+1,n);i++) sum+=a[i];
if(n==rem) return sum<=m;
q2.push(sum);
for(int i=rem+2;i<=n;i++) q1.push(a[i]);
}else{
for(int i=1;i<=n;i++) q1.push(a[i]);
}
while(true){
ll tmp=0;
for(int i=1;i<=k;i++){
if(q1.empty() && q2.empty()) break;
if(q1.empty()){
tmp+=q2.front();
q2.pop();
}
else if(q2.empty()){
tmp+=q1.front();
q1.pop();
}
else{
if(q1.front()>q2.front()){
tmp+=q2.front();
q2.pop();
}else{
tmp+=q1.front();
q1.pop();
}
}
}
sum+=tmp;
if(q1.empty() && q2.empty()) break;
q2.push(tmp);
}
return sum<=m;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>a[i];
sort(a+1,a+1+n);
int L=2,R=n+1,mid;
while(L<R){
mid=(L+R)/2;
if(check(mid))
R=mid;
else
L=mid+1;
}
cout<<L<<'\n';
return 0;
}