【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;
}
posted @ 2023-03-17 21:02  曙诚  阅读(31)  评论(0)    收藏  举报