原题题目:
题目背景
YCB要发送的一篇文章中有 n 种不同的单词,
从 1 到 n 进行编号,其中第 i 种单词出现的总次数为 ci。
YCB的电报机只能发送两种信号,其中 - (长)信号耗时为 2s ; . (短)信号耗时为 1s。
请你为YCB设计一种电报编码,使得他发送整篇文章的总耗时最短。
当然,为了准确无误地接收到信息,任何一个字符的编码串都不能是另一个的前缀。
编码串 Str1 被称为编码串 Str2 的前缀 , 当且仅当:
存在 \(1 ≤ t ≤ m\),使得 Str1 与 Str2[1..t] 相等。
其中, m 是字符串 Str2 的长度, Str2[1..t] 表示 Str2 的前t个字符组成的字符串。
输入输出格式
输入第一行为 n ,第二行 n 个数分别表示 c i 。
输出一行一个数,表示最小总耗时。
样例输入输出
样例输入
4
1 2 2 3
样例输出
22
样例说明
在一种可行的最优方案中,我们给四种字符的编码依次为\(--\) , \(-.\) , \(.-\) , \(..\)
,总耗时=1 × 4 + 2 × 3 + 2 × 3 + 3 × 2 = 22(sec)最优.
样例输入2
15
9 4 3 3 10 10 8 10 3 1 4 1 8 6 4
样例输出2
445
数据范围:
对于 15% 的数据, \(n ≤ 5\)
对于 40% 的数据, \(n ≤ 30\)
对于 55% 的数据, \(n ≤ 100\)
对于 100% 的数据, \(1≤ n ≤ 750, 1≤ ci ≤ 10^5\)
题目解法:
引论:
首先无包含前缀编码肯定是用哈夫曼树。
所以构建一棵哈夫曼树,然后分配编码即可。
有一个显然的贪心,把出现次数排序后,深度小的一定是匹配次数多的。
显然,每向下扩展一层,新增贡献为还未放置的数之和。
利用这个我们尝试构造哈夫曼树。
对于 15%(n<=5) 的数据:
用并查集暴力枚举一下树的形态,然后分配深度即可。
枚举树的形态部分的代码:
void dfs(int x,int ret){
if(x==n){
ans=min(ret,ans);
return;
}
for(RG int i=1;i<=n;i++)
for(RG int j=1;j<=n;j++)
if(fa[i]==i&&fa[j]==j&&i!=j){//把j合并到i中
fa[j]=i;
t[i]+=t[j];
dfs(x+1,ret+t[i]+t[j]);
//i向上编一层需要t[i]个,j向上编一层需要2*t[j]个
t[i]-=t[j];
fa[j]=j;
}
}
//t为当前节点的size大小。
对于 40%(n<=30) 的数据,
考虑以深度来动态规划:
由于每扩展一个点,会向深度+1与深度+2分别增加一个点。
所以设: \(f[i][j][a][b]\)表示处理到树的第i层,还剩前j小的数没有安置,本层有a个节点,下一层有b个节点。
那么枚举在本层选k个节点放置,剩下的节点扩展。转移为:
\[f[i][j][a][b] = min(f[i+1][j-k][b+(a-k)][k] + ∑c[t],t∈[1,j-k])\]
这样的时间复杂度为\(O(N^5)\),滚掉一维后空间复杂度为\(O(N^3)\)。
对于 55%(n≤100)的数据,
观察到上面转移中i就是去打酱油的。
我们不需要关心实际上到了多少层,只需要关心这一层放多少。
所以\(f[i][a][b]\)表示还剩前i小的数没有安置,本层有a个节点,下一层有b个节点。
转移与上面类似:
\[f[i][a][b] = min(f[i-k][b+(a-k)][k] + ∑c[t],t∈[1,j-k])\]
这样的时间复杂度为\(O(N^4)\),空间复杂度为\(O(N^3)\)。
对于 100%(n<=750) 的数据:
再观察发现上面枚举的k也是没卵用的。
我们再改变一下状态:
\(f[i][j][k]\)表示还剩前 i 小的数没有安置,本层有 j 个空位,下一层有 k 个空位。
注意:这里定义 空位是指还未确定用途,可以放数也可以扩展的点。
那么转移有两种:
第一种:本层不放,向下扩展一层:
\[f[i][k+j][j] = min(f[i][k+j][j],f[i][j][k] + ∑c[t],t∈[1,j])\]
第二种:在本层放下一个:
\[f[i-1][j-1][k] = min(f[i-1][j-1][k],f[i][j][k])\]
用这两种转移即可。
时间复杂度为\(O(N^3)\),滚掉一维后空间复杂度位\(O(N^2)\),可以跑过所有测试点。
满分AC代码:
#include<bits/stdc++.h>
#define RG register
#define IL inline
#define ll long long
#define gi(x) scanf("%lld",&x)
#define maxn 755
#define INF 1e16+7
using namespace std;
ll f[2][maxn][maxn],c[maxn],n;
int main(){
freopen("epic.in","r",stdin);
freopen("epic.out","w",stdout);
gi(n);
for(RG ll i = 1; i <= n; i ++)gi(c[i]);
sort(c+1,c+n+1);
for(RG ll i = 1; i <= n; i ++)c[i] += c[i-1];
memset(f,127,sizeof(f));
f[n&1][1][1] = c[n];
for(RG ll i = n; i >= 1; i --)
{
RG ll p = (i&1) , q = (p^1);
memset(f[q],127,sizeof(f[q]));
for(RG ll j = 0; j <= i; j ++)
for(RG ll k = 0; j+k <= i; k ++)
f[p][k+j][j] = min(f[p][k+j][j] , f[p][j][k] + c[i]);
for(RG ll j = 1; j <= i; j ++)
for(RG ll k = 0; j+k <= i; k ++)
f[q][j-1][k] = min(f[q][j-1][k] , f[p][j][k]);
}
cout<<f[0][0][0];
return 0;
}