《算法艺术与信息学竞赛》之 堆 最轻巧的语言

Alphabet Ak consists of k initial letters of English alphabet. A positive integer called a weight is assigned to each letter of the alphabet. A weight of a word built from the letters of the alphabet Ak is the sum of weights of all letters in this word. A language over an alphabet Ak is any finite set of words built from the letters of this alphabet. A weight of a language is the sum of weights of all its words. We say that the language is prefixless if for each pair of different words w, v from this language w is not a prefix of v.

We want to find out what is the minimal possible weight of an n-element, prefixless language over an alphabet Ak.

Example

Assume that k = 2, the weight of the letter a is W(a) = 2 and the weight of the letter b is W(b) = 5. The weight of the word ab is W(ab) = 2 + 5 = 7. W(aba) = 2 + 5 + 2 = 9. The weight of the language J = {ab, aba, b} is W(J) = 21. The language J is not prefixless, since the word ab is a prefix of aba. The lightest three-element, prefixless language over the alphabet A2 (assuming that weights of the letters are as before) is {b, aa, ab}; its weight is 16.

Task

Write a program that for each test case:

reads two integers n, k and the weights of k letters of an alphabet Ak;
computes the minimal weight of a prefixless, n-element language over the alphabet Ak;
outputs the result.
Input

The number of test cases t is in the first line of input, then t test cases follow separated by an empty line.

In the first line of a test case there are two positive integers n and k separated by a single space, (2 <= n <= 10000, 2 <= k <= 26). These are the number of words in a language and the number of letters in an alphabet respectively. The second line contains k positive integers separated by single spaces. Each of them is not greater than 10000. The i-th number is the weight of the i-th letter.

Output

For each test case you should output one line with the weight of the lightest prefixless n-element language over the alphabet Ak.

Example

Sample input:
1
3 2
2 5

Sample output:
16

提前声明:这个题的代码不是自己写的。
现在来讲一下这道题的思路:
首先我们定义prefixless语言为满足单词互不为前缀的语言,由于给定了值,我们要求权值最小的prefixless语言,就要想办法找出这样的单词,这显然有很多种,所以要用到字典树的一个思想。(这里不进行深入展开)每次可用单词即为字典树叶子节点上的单词。但是我们不需要实际建树,用数组模拟即可。
接下来要去n个单词,使其权值和最小,很自然想到贪心,然后可以用堆来实现。有人可能会误解,认为只要求出n个单词即可停止,因为我们是贪心做的,其实着都是因为样例,我自己写了一组例子,可以让大家看的更清晰。
共三个字母:a=2,b=5,c=100,求4个单词。接下来上一张图(电脑画的,手残勿喷,应该还是可以看懂的)
手残的博主画的巨丑的样例图
图中红色的是节点,绿色的是权值,一步一步来:
如果按照最开始所说的找出四个最小的就停止的话,就会出问题,会得到:aa,ab,b,c;权值还是比较大的,继续尝试,发现aaa,aab,ab,b这个解显然要更优秀,再次查找即可发现无法更新值了,所以我们的计算条件即为:拓展一个节点后最优解无法得到更新。
但是事情没有想象的那么简单,由于数据量较大,我们无法存下,会爆空间,所以我们要用到一个最大堆-最小堆优化。这个地方是重点,博主就是这个地方不会写,所以代码是看别人的。因为最多选n个元素嘛,所以堆里当然只要存n个就够了,每次我们选最小的n个,当第n+1个元素出现之后,我们只需要将最大的那个删除即可,所以我们要将元素同时插入到两个堆中,同时插入同时删除。这个优化做完后,这题差不多就可以过了。
代码是看了这样一片博客(貌似也是转的,但是原博客打不开了)http://blog.sina.com.cn/s/blog_89a06c7d0100u2or.html
下面贴上别人的代码:
注意,这个代码我是修改过一部分的,原博客中没有处理多种数据。

#include<cstdio>
#include<cstring>
using namespace std;
const int MaxL=10010;
int N,M;
int a[27];
int Len,ans,sum,Min[MaxL],Max[MaxL],Minid[MaxL],Maxid[MaxL];
inline void swap(int &x,int &y){
    int tmp=x;
    x=y;
    y=tmp;
}
void down_max(int i){
    int j=i*2;
    while(j<=Len){
        if(j<Len&&Max[j+1]>Max[j])
            j++;
            if(Max[i]<Max[j]){
                swap(Max[i],Max[j]);
                Minid[Maxid[i]]=j;
                Minid[Maxid[j]]=i;
                swap(Maxid[i],Maxid[j]);
                i=j;
                j=i*2;
            }
        else return;
    }
}
void down_min(int i){
    int j=i*2;
    while(j<=Len){
        if(j<Len&&Min[j+1]<Min[j])
            j++;
        if(Min[i]>Min[j]){
            swap(Min[i],Min[j]);
            Maxid[Minid[i]]=j;
            Maxid[Minid[j]]=i;
            swap(Minid[i],Minid[j]);
            i=j;
            j=i*2;
        }
        else return;
    }
}
void up_max(int j){
    int i=j/2;
    while(i){
        if(Max[i]<Max[j]){
            swap(Max[i],Max[j]);
            Minid[Maxid[i]]=j;
            Minid[Maxid[j]]=i;
            swap(Maxid[i],Maxid[j]);
            j=i;
            i/=2;
        }
        else return;
    }
}
void up_min(int j){
    int i=j/2;
    while(i){
        if(Min[i]>Min[j]){
            swap(Min[i],Min[j]);
            Maxid[Minid[i]]=j;
            Maxid[Minid[j]]=i;
            swap(Minid[i],Minid[j]);
            j=i;
            i/=2;
        }
        else return;
    }
}
void add(int val){
    if(Len<N){
        Len++;
        Min[Len]=val; Max[Len]=val;
        Minid[Len]=Len; Maxid[Len]=Len;
        up_min(Len); up_max(Len);
        sum+=val;
    }
    else if(Max[1]>val){
        sum=sum-Max[1]+val;
        int i=Maxid[1];
        Max[1]=val;
        Min[i]=val;
        down_max(1);
        down_min(i);
        up_min(i);
    }
}
void del(){
    int i=Minid[1];
    sum-=Min[1];
    if(1!=Len){
        Min[1]=Min[Len];
        Minid[1]=Minid[Len];
        Maxid[Minid[Len]]=1;
    }
    if(i!=Len){
        Max[i]=Max[Len];
        Maxid[i]=Maxid[Len];
        Minid[Maxid[Len]]=i;
    }
    Len--;
    down_min(1);
    down_max(i);
    up_max(i);
}
int main(){
    int T;
    scanf("%d",&T);
    while(T--){
        memset(a,0,sizeof(a));
        scanf("%d%d",&N,&M);
        sum=Len=0;
        ans=0x7FFFFFFF;
        for(int i=1;i<=M;++i)
            scanf("%d",a+i);
        for(int i=1;i<M;++i)
            for(int j=i+1;j<=M;++j)
                if(a[i]>a[j])
                    swap(a[i],a[j]);
        for(int i=1;i<=M;++i)
            add(a[i]);
        do{
            if(Len==N&&ans>sum)
                ans=sum;
            int t=Min[1];
            del();
            for(int i=1;i<=M;++i)
                add(t+a[i]);
            if(Len==N&sum>=ans) break;
        }while(1);
        printf("%d\n",ans);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值