【CF700D Huffman Coding on Segment】(Huffman 树+根号分治+莫队)
题意
给一个长为 \(n\) 的串,有 \(q\) 次询问,每次询问一个区间的最小二进制编码长度,即在可以唯一还原的前提下,将这一段子串转化为长度最小的二进制编码。
注意:可以将一个串对应到空串
$ 1 \leq n,q \leq 10^5 $。
思路
看到二进制编码,很快可以想到 Huffman 树。设字符 \(i\) 的出现次数为 \(f_i\),\(i\) 在 Huffman 树上的深度为 \(d_i\) (根据题意,可以对应到空串,故根节点的深度为 \(0\)),那么每次询问就是求 \(\sum f_i d_i\) 的最小值。
如果直接暴力将所有字符的出现次数入队合并,单次询问的复杂度可以达到 \(O(n \log n)\)。注意到可能会有很多字符的出现次数相同,考虑从这方面入手。
由于随着单个字符出现次数的增加,总出现的字符数会呈现减少的趋势,于是可以用到根号分治。
具体地,我们可以设定一个阈值 \(B=\sqrt{n}\)。
-
当字符的出现次数小于等于 \(B\) 时,直接枚举出现次数为 \(i\) 的字符有多少种,然后合并,这里的复杂度为 \(O(\sqrt{n})\)。
-
当字符的出现次数大于 \(B\) 时,直接按照一般的合并 Huffman 树的方式,将出现次数入队,由于出现次数超过 \(B\) 的字符数不超过 \(\sqrt{n}\),那么这部分的复杂度为 \(O(\sqrt{n} \log n )\)。
于是总的时间复杂度就为 \(O(n \sqrt{n} \log n)\),常数比较小。
code:
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<vector>
using namespace std;
const int N=1e5+10,M=2e5+10,B=405;
struct Query{int l,r,id;}q[N];
int ans[N],n,m,id[N],a[N],tmp[N],cnt[N],f[M*5];//f[i]表示出现次数为i的字符有多少个
bool cmp(Query x,Query y){return id[x.l]==id[y.l]?x.r<y.r:x.l<y.l;}
priority_queue<int,vector<int>,greater<int> >Q;
vector<int>v;
void update(int x,int k){f[cnt[a[x]]]--;cnt[a[x]]+=k;f[cnt[a[x]]]++;}
int solve()
{
for(auto i:v) if(cnt[i]>B) Q.push(cnt[i]);
for(int i=1;i<=B;i++) tmp[i]=f[i];int ans=0,now=0;
for(int i=1;i<=B;i++) if(tmp[i])
{
if(now)
{
if(i+now>B) Q.push(i+now);else tmp[i+now]++;
tmp[i]--,ans+=i+now,now=0;
}
if(tmp[i]&1) tmp[i]--,now=i;
ans+=tmp[i]*i;
if((i*2)>B)
{
for(int j=tmp[i]/2;j;j--) Q.push(i*2);
}
else tmp[i*2]+=tmp[i]/2;
}
if(now) Q.push(now);
while(!Q.empty())
{
int u=Q.top();Q.pop();if(Q.empty()) continue;
u=u+Q.top();Q.pop();ans+=u;Q.push(u);
}
return ans;
}
int main()
{
// freopen("1.in","r",stdin);
// freopen("1.out","w",stdout);
scanf("%d",&n);for(int i=1;i<=n;i++) scanf("%d",&a[i]),cnt[a[i]]++,id[i]=(i-1)/B+1;scanf("%d",&m);
for(int i=1;i<N;i++) if(cnt[i]>B) v.push_back(i);memset(cnt,0,sizeof(cnt));
for(int i=1;i<=m;i++) scanf("%d%d",&q[i].l,&q[i].r),q[i].id=i;sort(q+1,q+m+1,cmp);
for(int l=1,r=0,k=1;k<=m;k++)
{
while(r<q[k].r) update(++r,+1);
while(l>q[k].l) update(--l,+1);
while(l<q[k].l) update(l++,-1);
while(r>q[k].r) update(r--,-1);
ans[q[k].id]=solve();
}
for(int i=1;i<=m;i++) printf("%d\n",ans[i]);
return 0;
}

浙公网安备 33010602011771号