洛谷P2168 [NOI2015]荷马史诗

题意:

一部《荷马史诗》中有n种不同的单词,从1到n进行编号。其中第i种单词出现的总次数为wi。Allison 想要用k进制串si来替换第i种单词,使得其满足如下要求:

对于任意的 1 ≤ i, j ≤ n , i ≠ j ,都有:si不是sj的前缀。

如何选择si,才能使替换以后得到的新的《荷马史诗》长度最小。在确保总长度最小的情况下,Allison 还想知道最长的si的最短长度是多少?

思路:si不是sj的前缀,想到编码问题,构造k叉哈夫曼树

哈夫曼树的每个叶子节点有一个权值,最小化所有叶子节点*叶子到根的距离的乘积的和,贪心:令权值大的叶子节点尽可能深度小

这样构造,从根到每一个叶子节点的路径唯一,保证si不是sj的前缀

注意,这里的叶子节点的权值即为第i种单词出现的总次数为wi

实现:贪心用优先队列,一开始每个节点作为一棵深度为1,只有一个根节点的树,我们要做的就是每次从中取前k小的节点合并成一个节点,再插入森林中,新节点深度+1

因此优先队列维护一个二元组(节点权值,深度)

注意:若n个点不能被恰好k次后就剩一个节点,则需要补0节点

code

#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int inf=0x7fffffff;
const int maxn=500005;

int n,k;
LL cnt=0,ans=0;

struct node{
    LL w,h;
    bool operator < (const node& b) const{
        if(w!=b.w) return w>b.w;
        return h>b.h;
    }
};

priority_queue<node>q;

LL read(){
    LL x=0,f=1; char c=getchar();
    while(c>'9'||c<'0'){
        if(c=='-') f=-1;
        c=getchar();
    }
    while(c>='0'&&c<='9'){
        x=(x<<1)+(x<<3)+c-'0';
        c=getchar();
    }
    return x*f;
}

int main(){
    n=read(); k=read();
    for(int i=1;i<=n;i++){
        LL x=read();
        q.push((node){x,1});
    }
    if((n-1)%(k-1)!=0) cnt=k-1-(n-1)%(k-1);
    for(int i=1;i<=cnt;i++){
        q.push((node){0,1});
    }
    cnt+=n;
    while(cnt>1){
        LL tot=0,mx=0;
        for(int i=1;i<=k;i++){
            tot+=q.top().w;
            mx=max(mx,q.top().h);
            q.pop();
        }
        ans+=tot;
        q.push((node){tot,mx+1});
        cnt-=k-1;
    }
    printf("%lld\n%lld\n",ans,q.top().h-1);
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/xvzichen/p/11789288.html