[NOI2015]荷马史诗 - Huffman树

lindongli2004

2019-11-26 22:21:09

Solution

简化题意: 构造一棵包含n个叶子节点的 $k$ 叉树 , 第 $i$ 个叶子节点深度为 $dep[i]$ , 权值为 $w[i]$ 。 $1.$ 最小化 $\sum_{i=1}^n w[i] \times dep[i]$ . $2.$ 在满足此条件下 , 最小化 $max_{i=1}^n{dep[i]}$ . 先来说明简化后的题意与原问题等价:设每个字母代表树中的一个结点,又因为每个单词都以一个叶子结束,所以保证了单词互不相同。 考虑条件$1$ , 满足此条件的树叫 **最优二叉树** , 别称 **Huffman** 树 。 先考虑 $k=2$ 的情况,我们可以贪心让权值小的叶子深度尽量大。 自下而上建树,具体步骤如下: 1. 将 n 个叶子节点插入一个小根堆。 2. 取出2个权值最小的叶子 a, b ,新建一个结点 c ,则: - c为Huffman树上点a,b的父亲 - w[c]=w[a]+w[b] 3. 将c插入小根堆,重复执行 2 操作直至 n=1 。 至此,$k=2$ 的情况已经得到解决,下面考虑 $k>2$ 的情况。 上述贪心不能解决 $k>2$ 的情况,是因为最后一次 $2$ 操作可能 $n<k$ 导致此时从任意一层取一个叶子节点到该层都比此方案优。 解决这个问题的方案为:虚拟添加若干个权值为 $0$ 的叶子结点,使得 $(n-1) \mod (k-1)=0$ ,上述问题得以解决。 再考虑条件$2$:记录每个结点子树的深度,在权值相等的结点中,选择子树深度较小的结点合并即可。 下面贴 [[NOI2015]荷马史诗](https://www.luogu.com.cn/problem/P2168) 的代码 ```cpp #include<iostream> #include<cstdio> #include<queue> using namespace std; const int N=1002019; #define int long long int n,k,ans,mxd,tot,val[N],v[N]; struct Edge{int to,next;}e[N<<1]; void add(int x,int y){ e[++tot].to=y; e[tot].next=v[x]; v[x]=tot; } struct node{ int id,w,dep; // id :编号, w :权值, dep :子树深度 . node(int a,int b,int c){id=a;w=b;dep=c;}; bool operator >(const node &rhs)const{ return rhs.w==w?rhs.dep<dep:rhs.w<w; } }; priority_queue<node,vector<node>,greater<node> > q; void dfs(int x,int d){ // 统计答案 if(x<=n)ans+=val[x]*d,mxd=max(mxd,d); for(int p=v[x];p;p=e[p].next)dfs(e[p].to,d+1); } int read(){ int x=0,f=1; char ch=getchar(); while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0' && ch<='9'){x=x*10+ch-'0';ch=getchar();} return x*f; } #undef int int main() { #define int long long n=read(); k=read(); for(int i=1;i<=n;i++) q.push(node(i,val[i]=read(),0)); while((n-1)%(k-1)!=0)q.push(node(++n,0,0)); // 插入若干虚拟结点 int id=n,n1=n; while(n1-=k-1,n1>=1){ int th=0,mx=0; ++id; // 新建编号为 id 的点 th for(int i=1;i<=k;i++,q.pop()){ // 取出 k 个最优元素 node nw=q.top(); th+=nw.w; mx=max(mx,nw.dep); add(id,nw.id); } q.push(node(id,th,mx+1)); // 插入该结点 } dfs(id,0); // 统计答案 printf("%lld\n%lld",ans,mxd); return 0; } ```