Living-Dream 系列笔记 第68期

Posted on 2024-07-29 11:08  _XOFqwq  阅读(4)  评论(0编辑  收藏  举报

带权路径长度:$ 节点权值 \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;
}