题解——[NOI2015]荷马史诗

题面:

追逐影子的人,自己就是影子 ——荷马

Allison 最近迷上了文学。她喜欢在一个慵懒的午后,细细地品上一杯卡布奇诺,静静地阅读她爱不释手的《荷马史诗》。但是由《奥德赛》和《伊利亚特》 组成的鸿篇巨制《荷马史诗》实在是太长了,Allison 想通过一种编码方式使得它变得短一些。

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

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

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

一个字符串被称为k进制字符串,当且仅当它的每个字符是 0 到 k − 1 之间(包括 0 和 k − 1 )的整数。

字符串 str1 被称为字符串 str2 的前缀,当且仅当:存在 1 ≤ t ≤ m ,使得str1 = str2[1..t]。其中,m是字符串str2的长度,str2[1..t] 表示str2的前t个字符组成的字符串。

看数据范围及样例

题解:

如果学过huffman tree这题就不难了!

当然只知道huffman tree还是不行的,还要知道哈夫曼编码,

哈夫曼树貌似一开始就是用来获取哈夫曼编码的?

这个网上讲解很多,就不在多讲。

此题中如果按照出现次数为权值来构建哈夫曼树,则总长度一定最小,

原版哈夫曼树是2进制的,因此只能放2叉,

但这里是k进制,所以就可以放k叉了,

所以每次用优先队列找出前k小然后合并即可

但是要注意如果k != 2的话,就要保证每次合并都有k个节点(直到最后合并成一个),

所以要在最开始补一些空节点进去,

因为每次都是拿出k个,放入一个,所以实际上是消掉了(k-1)个,

然后最后需要剩下一个,因此我们需要n % (k-1) == 1,

然后补齐需要的即可

那为什么用出现次数作为权值就最优呢?

因为出现次数越大的单词越浅,所以编码长度也越小,自然总长度就最小了

但是这里还需要注意一个东西,就是第二问的要求,

需要最长的编码最短,依旧是用贪心的思想,

在权值相同的情况下,每次优先合并被合并次数少的节点(最深的节点更浅)

  1 #include<bits/stdc++.h>
  2 using namespace std;
  3 #define R register int
  4 #define    AC 100100
  5 #define ac 500100
  6 #define getchar() *o++
  7 #define LL long long 
  8 char READ[5000100],*o=READ;
  9 int n,k,id,deep;
 10 int d[ac];//w为点权(包括新增点)
 11 int Head[ac],Next[ac],date[ac],tot;
 12 LL ans;
 13 LL w[ac];//error!!!权值很大的啊
 14 bool done;
 15 struct cmp1{
 16     bool operator () (int &a,int &b)
 17     {
 18         if(w[a] != w[b])    return w[a] > w[b];
 19         else return d[a] > d[b];
 20     }
 21 };
 22 
 23 priority_queue<int,vector<int>,cmp1>q;
 24 /*哈弗曼编码,这样是最短的,但无需获取具体的编码,
 25 只要获得每个单词对应的出现次数(题目给定)和编码长度(哈弗曼树深度)即可,
 26 因为是k进制,所以建立k叉哈弗曼树,最后dfs一遍获取每个叶节点的深度,
 27 然后对出现次数与深度的积求和即可*/
 28 inline LL read()
 29 {
 30     LL x=0;char c=getchar();
 31     while(c > '9' || c < '0') c=getchar();
 32     while(c >= '0' && c <= '9') x=x*10+c-'0',c=getchar();
 33     return x;
 34 }
 35 
 36 inline void add(int f,int w)
 37 {
 38     date[++tot]=w,Next[tot]=Head[f],Head[f]=tot;
 39 }
 40 
 41 void dfs(int x,int dep)
 42 {
 43     int now;
 44     if(!Head[x])
 45     {
 46         ans+=(LL)dep * w[x];
 47         if(w[x]) deep=max(deep,dep);
 48         return ;    
 49     }
 50     for(R i=Head[x]; i ;i=Next[i])
 51     {
 52         now=date[i];
 53         dfs(now,dep+1);
 54     }
 55 }
 56 
 57 bool build()
 58 {
 59     if(done)
 60         if(q.empty() || q.size() == 1) return false;
 61     int x=++id,now;//动态加点
 62     for(R i=1;i<=k;i++)
 63     {
 64         if(q.empty()) break;
 65         now=q.top();
 66         q.pop();
 67         w[x] += w[now];
 68         d[x] = max(d[x] , d[now]);
 69         add(x,now);//建树
 70     }
 71     ++d[x];//这次合并也要算啊
 72     q.push(x);
 73     done=true;
 74     return true;
 75 }
 76 
 77 void pre()
 78 {
 79     n=read(),k=read();
 80     id=n;
 81     for(int i=1;i<=n;i++) 
 82     {
 83         w[i]=read();
 84         q.push(i);
 85     }
 86     if(k != 2)//但是只有不为二叉的时候才要考虑这些
 87     {
 88         int now=0;
 89         if(!(n % (k-1)))//当余数为0的时候无需再跨越一次,,,, 
 90         {
 91             q.push(++id);
 92         } 
 93         else
 94         if(n % (k-1) != 1) //huffman要求完美合并?
 95         {
 96             now += (k-1) - (n % (k-1)) + 1;
 97             for(R i=1;i<=now;i++) q.push(++id);//error!!!是权值为0,不是编号为0!!!
 98         }
 99     }
100 }
101 
102 void work()
103 {
104     while(build());
105     dfs(id,0);
106     printf("%lld\n",ans);
107     printf("%d\n",deep);
108 }
109 
110 int main()
111 {
112 //    freopen("in.in","r",stdin);
113     fread(READ,1,5000000,stdin);
114     pre();
115     work();
116 //    fclose(stdin);
117     return 0;
118 }

猜你喜欢

转载自www.cnblogs.com/ww3113306/p/9143281.html
今日推荐