Living-Dream 系列笔记 第68期
带权路径长度:$ 节点权值 \times 节点到根的距离 $。
哈夫曼树:在叶子节点的节点权值为一个给定序列的前提下,树上所有节点带权路径长度之和最小的二叉树。
构造:每次从原序列选取两个权值最小的两个点,将它们父节点的权值设为两个子节点权值之和。
性质:
-
哈夫曼树一定是满二叉树(不是完美二叉树,是每个节点要么有两个儿子,要么无儿子的二叉树)。
证明:若一个哈夫曼树只有一个儿子,则可以给它加上另一个儿子使得该儿子的节点权值为 \(0\),则该儿子不会影响整体带权路径长度之和最小。
-
离根越近,节点权值越大。
证明:从构造方式易证。
-
$ 非叶子节点的点权和 = 叶子节点的带权路径和 $。
证明:参考合并果子这题,容易发现哈夫曼树的构造方式即为合并的过程,非叶子节点的点权和 与 叶子节点的带权路径和 在这题中都表示 最小的体力耗费值,自然也就相等。
-
当叶子节点数为 \(n\) 时,总节点数为 \(2n-1\)(所有满二叉树都符合该性质)。
证明:设 \(n_x\) 表示有 \(x\) 个儿子的节点的个数,\(m\) 为二叉树的总边数,\(n\) 为二叉树的总节点数。于是有
\[\begin{cases} n=n_0+n_2\\ m=n-1\\ m=n_2 \times 2 \end{cases} \]将一式代入二式,并联立二、三式可得 \(n_2=n_0-1\)。
所以 \(n=n_0+n_2=n_0+n_0-1=2 \times n_0 -1\)。
POJ 1521
最小的压缩长度即为哈夫曼树非叶子节点的点权之和,维护小根堆模拟建哈夫曼树的过程即可。
code
#include<iostream> #include<string.h> #include<queue> #include<iomanip> using namespace std; const int N=1e7+5,M=31; string s; int ans,cnt[M]; int main(){ while(1){ cin>>s; if(s=="END") break; memset(cnt,0,sizeof cnt); priority_queue<int,vector<int>,greater<int> > pq; ans=0; for(int i=0;i<s.size();i++){ if(s[i]=='_') cnt[26]++; else cnt[s[i]-'A']++; } for(char i=0;i<=26;i++) if(cnt[i]) pq.push(cnt[i]); if(pq.size()==1) ans=s.size(); while(pq.size()>=2){ int x=pq.top(); pq.pop(); int y=pq.top(); pq.pop(); pq.push(x+y); ans+=x+y; } int last=s.size()*8; cout<<last<<' '<<ans<<' '<<fixed<<setprecision(1)<<(double)(last)/(double)(ans)<<'\n'; } return 0; }
P2168
众所周知,\(k\) 叉哈夫曼树是每 \(k\) 个节点合并为一个节点,但可能最后只剩下不足 \(k\) 个节点,因此就无法合成根节点。
这说明有节点可以深度更小,带权路径更小。
于是我们考虑补叶子节点并使得它们的节点权值均为 \(0\)。
每 \(k\) 个节点合并为 \(1\) 点,相当于每次减少 \(k-1\) 个节点,因此总节点 \(n\) 必须满足 \((n-1) \bmod (k-1)=0\)。
然后构建哈夫曼树即可完成第一问。
然后小根堆维护深度,并将其作为第二关键字处理合并即可完成第二问。
code
#include<bits/stdc++.h> #define int long long using namespace std; int n,k,ans; priority_queue<pair<int,int>> pq; signed main(){ cin>>n>>k; for(int i=1,x;i<=n;i++){ cin>>x; pq.push(make_pair(-x,0)); } while((n-1)%(k-1)!=0){ n++; pq.push(make_pair(0,0)); } while(pq.size()>1){ int sum=0,w=0; for(int i=1;i<=k;i++){ sum+=pq.top().first; w=min(w,pq.top().second); pq.pop(); } ans+=-sum; pq.push(make_pair(sum,w-1)); } cout<<ans<<'\n'<<-pq.top().second; return 0; }
CF884D
显而易见的,题目中的分球过程实质是三叉哈夫曼树的反构造过程。
于是将上一题的 \(k\) 令其恒为 \(3\) 即可。
code
#include<bits/stdc++.h> using namespace std; struct node { long long w, dep; bool operator < (const node &b) const { if(w != b.w) return w > b.w; return dep > b.dep; } }; priority_queue<node> pq; int n, k, cnt; long long ans; int main() { cin >> n; k = 3; for(int i = 1; i <= n; i++) { long long x; cin >> x; pq.push((node){x, 1}); } if((n-1) % (k-1) != 0) cnt = k - 1 - (n-1) % (k-1); for(int i = 1; i <= cnt; i++) pq.push((node){0, 1}); cnt = cnt + n; while(cnt >= 2) { long long cur = 0, maxi = 0; for(int i = 1; i <= k; i++) { cur += pq.top().w; maxi = max(maxi, pq.top().dep); pq.pop(); } ans += cur; pq.push((node){cur, maxi+1}); cnt -= k-1; } cout << ans; return 0; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!