原址:http://blog.csdn.net/Quack_quack/article/details/46958413
题目大意:给出n个数字w[],代表n个字母出现的次数,给出k。要求用k进制的数字串si替换第i个字母,且替换之后要求替换后的文章无二义性(这里的无二义性是指对于任意的 1≤i,j≤n ,i≠j,都有: si不是sj的前缀),求替换后最短的文章的长度(长度len=sigma(w[i]*strlen(si)))和这种情况下最大的si的最小值。
数据范围:n<=100000,k<=9
分析:
题目要求的限制条件很多,既要求替换后无二义性,又要求方案的最值,还有k进制的限制= =。没学过哈夫曼树的能想到就太厉害了,学过的(NOI这个级别肯定也学过)能想起来然后套模型就有思路了。
我很弱,因此直接套了哈夫曼树的模型。
哈夫曼树一般是二叉树,建树的方法就是每次选择两个权值(即出现次数)最小的点,删除这2个点,加入一个权值是这两个点之和的新点进去。并且使这被删除的2个点的父亲成为那个新点。
编码的时候左支和右支一个是1一个是0,从根节点到叶子节点经过的边的1/0序列就是叶子节点对应的编码。
然而这个题是k叉树,方法和上面类似,然而每次选择k个权值最小的点的时候容易让最后一次合并的时候的点不足k个。假设最初有n个点,最后有1个点,每次合并删除k个点又放进1个点。那么易得:(n-1)是(k-1)的倍数。如果(n-1)%(k-1)!=0,那么就要再放入(k-1-(n-1)%(k-1))个虚拟点,并且它们的权值为0,它们也参与求最小k个点。
然而此题还要求si的最大值最小,因此我们让点代表一个二元组(val,dep),表示这个点的权值和点在树中的深度。在求最小k个点时,把val作为第一比较条件,如果val值相等,则把dep小的放在前面,这样在每次合并的时候,深度小的点都会被优先合并,保证了根到叶子的最长链的长度尽量小。
所以,可以得到此题的算法:
1)处理这n个权值,加入虚拟点,这些点的val值上文已经告诉,dep值为0,ans=0;
2)每次取出前k小的点,求它们的val之和sum,求它们的dep的最大值d,那么放入的新点应该是(sum,d+1),把它放入原来的容器里面并要求有序,且ans+=sum(画一棵哈夫曼树,想想求文章长度的过程能这么实现的原理);
3)当容器内只有一个点时,输出ans和这个点的dep值。
这样的话正确性可以保证,但是注意容器的选择,不能直接数组模拟,会超时,可以用堆优化,我用的优先队列,和堆差不多,这样维护点的有序性变成O(log n)。求前k大的数也不用什么高级的数据结构,考虑k不大,就优先队列一个一个弹出,弹k次就可以了。
最终时间复杂度是O(nlogn)。
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<vector>
#include<utility>
#include<queue>
using namespace std;
typedef long long LL;
LL sum=0,mark;
int n,k;
typedef pair<LL,LL> Int;
struct cmp{
bool operator() (const Int a,const Int b) const{
if(a.first!=b.first) return a.first>b.first;
return a.second>b.second;
}
};
priority_queue< Int,vector<Int>,cmp >a;
int main()
{
scanf("%d %d",&n,&k);
for(int i = 1;i <= n;i ++) {
Int x;
x.second=1;
scanf("%lld",&x.first);
a.push(x);
}
LL top=0;
if((n-1)%(k-1) != 0) top += k-1-(n-1)%(k-1);
for(int i=1;i<=top;i++){
Int x;
x.first=0;
x.second=1;
a.push(x);
}
top+=n;
while(top!=1){
LL num=0;
mark=0;
Int x;
for(LL i=1;i<=k;i++){
x = a.top();
num+=x.first;
mark=max(mark,x.second);
a.pop();
}
sum+=num;
x.first=num;
x.second=mark+1;
a.push(x);
top-=k-1;
// printf("dot:%d,%d\n",p.first,p.second);
}
printf("%lld\n%lld",sum,mark);
return 0;
}