Codeforces 题目传送门 & 洛谷题目传送门

好家伙,刚拿到此题时我连啥是 huffman 编码都不知道

一种对 \(k\) 个字符进行的 huffman 编码的方案可以看作一个由 \(k\) 个叶节点组成的二叉树,从根节点开始走到左儿子相当于在字符串后面添一个 \(0\),走到右儿子相当于在字符串后面添一个 \(1\),那么一个叶子节点就对应着一个字符的编码。

huffman 编码的一个很经典的问题是,我们现在对每个字符定义了一个频率 \(f_i\),我们需构造出一棵有 \(k\) 个叶子节点的二叉树并将每个字符与一个叶子节点对应,假设第 \(i\) 种字符对应的叶子节点的深度为 \(d_i\),现在我们需最小化 \(\sum\limits_{i=1}^kf_id_i\)。

这个问题可以用贪心+堆来解决,我们考虑初始 \(k\) 个点都单独成一棵森林,每次要合并两个节点,也就是新建一个节点,左右儿子分别是待合并的两个节点,新节点的权值为两个节点的权值和。由于两个节点进行了一次合并,它们的深度都增加了 \(1\),故答案增加了两个节点的点权之和。如此操作下去,合并 \(k-1\) 次之后就可以得到一棵二叉树。显然根据贪心的思想,每次合并的两个节点应当为所有点点权值最小的两个节点。这个过程可以用堆优化,复杂度 \(k\log k\)。

代码大概长这样:

while(q.size()>1){
int x=q.top();q.pop();
x+=q.top();q.pop();
ret+=x;q.push(x);
}

回到本题来,显然求出区间内每个数的出现次数是必须求出的,这可以用莫队维护,复杂度 \(m\sqrt{n}\)。假设 \(cnt_i\) 为 \(i\) 出现的次数。本题的难点就在于知道每个数的出现次数之后怎样计算答案,显然每次询问都重复一遍上面的操作复杂度最坏可达 \(qn\log n\),会炸。不过 \(cnt\) 数组有一个很好的性质,那就是 \(\sum cnt_i=r-l+1\),也就是说所有 \(cnt_i\) 的总和不会太大,这启发我们用根号分治的思想,我们设一个阈值 \(B\),然后考虑这样两个暴力:

  • 对于 \(cnt_i>B\) 的数,这样的数至多只有 \(\dfrac{n}{B}\) 个,对于这样的数我们暴力跑一遍上面的过程即可。
  • 对于 \(cnt_i\leq B\) 的数,这样的数可能很多,不过这些数的出现次数只有 \(\mathcal O(B)\) 级别,故我们转而枚举出现次数 \(c\),我们建立一个桶 \(b_c\) 表示出现次数为 \(c\) 的数有多少个(\(b\) 数组可以在莫队 push/pop 的过程中 \(\mathcal O(1)\) 求出)。假设我们现在枚举到出现次数为 \(c\) 的数,我们将这 \(b_c\) 个数两两配对并合并成 \(2c\),显然会合并出 \(\lfloor\dfrac{b_c}{2}\rfloor\) 个 \(2c\),对答案产生 \(\lfloor\dfrac{b_c}{2}\rfloor·2c\) 的贡献。由于经过我们这么一合并,出现次数为 \(2c\) 的数多了 \(\lfloor\dfrac{b_c}{2}\rfloor\),故令 \(b_{2c}\leftarrow b_{2c}+\lfloor\dfrac{b_c}{2}\rfloor\),特别地,如果 \(2c>B\),那么我们就暴力把这 \(\lfloor\dfrac{b_c}{2}\rfloor\) 个 \(2c\) 插入优先队列,到时候与原本出现次数就大于 \(B\) 的数一起合并。另外,如果 \(b_c\) 为奇数,那么意味着会有一个落单的 \(c\),我们实时维护一个变量 \(pre\) 记录落单的值是什么,我们扫描到 \(c\) 时如果发现临时变量不为 \(0\) 就首先拎出一个 \(c\) 出来与 \(pre\) 合并成 \(pre+c\),即如果 \(pre+c\leq B\) 就令 \(b_{pre+c}\leftarrow b_{pre+c}+1\),否则将 \(pre+c\) 压入优先队列。细节有亿点多,具体见代码罢。

算下复杂度,对于 \(cnt_i>B\) 的数,复杂度显然是 \(\dfrac{n}{B}\log\dfrac{n}{B}\) 的,毫无疑问。对于 \(cnt_i\leq B\) 的数扫描一遍也是 \(\mathcal O(B)\) 的。重点在于每次把这 \(\lfloor\dfrac{b_c}{2}\rfloor\) 个 \(2c\) 插入优先队列,最多会插多少次。不难发现我们每次合并前后,\(\sum c·b_c\) 始终是定值,为 \(r-l+1\),也就是说当我们合并完出现次数 \(\leq B\) 的数后依旧有 \(\sum c·b_c=r-l+1\),故插入优先队列的数还是 \(\dfrac{n}{B}\) 级别的,复杂度即为 \(B+\dfrac{n}{B}\log\dfrac{n}{B}\)

取 \(B=\sqrt{n\log n}\) 即可实现 \(\sqrt{n\log n}\) 实现询问,总复杂度 \(m\sqrt{n}+m\sqrt{n\log n}=m\sqrt{n\log n}\),可通过此题。

#include <bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define fill0(a) memset(a,0,sizeof(a))
#define fill1(a) memset(a,-1,sizeof(a))
#define fillbig(a) memset(a,63,sizeof(a))
#define pb push_back
#define ppb pop_back
#define mp make_pair
template<typename T1,typename T2> void chkmin(T1 &x,T2 y){if(x>y) x=y;}
template<typename T1,typename T2> void chkmax(T1 &x,T2 y){if(x<y) x=y;}
typedef pair<int,int> pii;
typedef long long ll;
namespace fastio{
#define FILE_SIZE 1<<23
char rbuf[FILE_SIZE],*p1=rbuf,*p2=rbuf,wbuf[FILE_SIZE],*p3=wbuf;
inline char getc(){return p1==p2&&(p2=(p1=rbuf)+fread(rbuf,1,FILE_SIZE,stdin),p1==p2)?-1:*p1++;}
inline void putc(char x){(*p3++=x);}
template<typename T> void read(T &x){
x=0;char c=getchar();T neg=0;
while(!isdigit(c)) neg|=!(c^'-'),c=getchar();
while(isdigit(c)) x=(x<<3)+(x<<1)+(c^48),c=getchar();
if(neg) x=(~x)+1;
}
template<typename T> void recursive_print(T x){if(!x) return;recursive_print(x/10);putc(x%10^48);}
template<typename T> void print(T x){if(!x) putc('0');if(x<0) putc('-'),x=~x+1;recursive_print(x);}
void print_final(){fwrite(wbuf,1,p3-wbuf,stdout);}
}
const int MAXN=1e5;
const int MAX_BLK=316;
const int MAX_BLK2=1400;
int n,qu,a[MAXN+5],cnt[MAXN+5],cnt_cnt[MAXN+5],tmp[MAX_BLK2+5];
int blk,blk_cnt,blk_v,L[MAX_BLK+2],R[MAX_BLK+2],bel[MAXN+5];
vector<int> tt;ll ret[MAXN+5];
void ins(int v){cnt_cnt[cnt[v]]--;cnt[v]++;cnt_cnt[cnt[v]]++;}
void del(int v){cnt_cnt[cnt[v]]--;cnt[v]--;cnt_cnt[cnt[v]]++;}
ll deal(){
priority_queue<int,vector<int>,greater<int> > q;
for(int i=0;i<tt.size();i++) if(cnt[tt[i]]>blk_v) q.push(cnt[tt[i]]);
for(int i=1;i<=blk_v;i++) tmp[i]=cnt_cnt[i];int pre=0;ll ret=0;
for(int i=1;i<=blk_v;i++) if(tmp[i]){
if(pre){
ret+=pre+i;tmp[i]--;
if(pre+i<=blk_v) tmp[pre+i]++;
else q.push(pre+i);
pre=0;
} ret+=1ll*i*(tmp[i]/2*2);
if(i+i<=blk_v) tmp[i+i]+=tmp[i]/2;
else for(int j=1;j<=tmp[i]/2;j++) q.push(i+i);
if(tmp[i]&1) pre=i;
}
if(pre) q.push(pre);
while(q.size()>1){
int x=q.top();q.pop();x+=q.top();q.pop();
ret+=x;q.push(x);
} return ret;
}
struct query{
int l,r,id;
bool operator <(const query &rhs){
if(bel[l]!=bel[rhs.l]) return bel[l]<bel[rhs.l];
return r<rhs.r;
}
} q[MAXN+5];
int main(){
scanf("%d",&n);for(int i=1;i<=n;i++) scanf("%d",&a[i]),cnt[a[i]]++;
blk=(int)pow(n,0.5);blk_v=(int)sqrt(n*log(n)/log(2));blk_cnt=(n-1)/blk+1;
for(int i=1;i<=MAXN;i++) if(cnt[i]>blk_v) tt.pb(i);memset(cnt,0,sizeof(cnt));
for(int i=1;i<=blk_cnt;i++){
L[i]=(i-1)*blk+1;R[i]=min(i*blk,n);
for(int j=L[i];j<=R[i];j++) bel[j]=i;
} scanf("%d",&qu);
for(int i=1;i<=qu;i++) scanf("%d%d",&q[i].l,&q[i].r),q[i].id=i;
sort(q+1,q+qu+1);int cl=1,cr=0;
for(int i=1;i<=qu;i++){
while(cr<q[i].r) ins(a[++cr]);
while(cl>q[i].l) ins(a[--cl]);
while(cl<q[i].l) del(a[cl++]);
while(cr>q[i].r) del(a[cr--]);
ret[q[i].id]=deal();
}
for(int i=1;i<=qu;i++) printf("%lld\n",ret[i]);
return 0;
}

Codeforces 700D - Huffman Coding on Segment(莫队+根分)的更多相关文章

  1. 【CodeForces】700 D. Huffman Coding on Segment 哈夫曼树+莫队+分块

    [题目]D. Huffman Coding on Segment [题意]给定n个数字,m次询问区间[l,r]的数字的哈夫曼编码总长.1<=n,m,ai<=10^5. [算法]哈夫曼树+莫 ...

  2. Codeforces 351D Jeff and Removing Periods(莫队+区间等差数列更新)

    题目链接:http://codeforces.com/problemset/problem/351/D 题目大意:有n个数,每次可以删除掉数值相同并且所在位置成等差数列的数(只删2个数或者只删1个数应 ...

  3. Codeforces 617E XOR and Favorite Number莫队

    http://codeforces.com/contest/617/problem/E 题意:给出q个查询,每次询问区间内连续异或值为k的有几种情况. 思路:没有区间修改,而且扩展端点,减小端点在前缀 ...

  4. codeforces 220B . Little Elephant and Array 莫队+离散化

    传送门:https://codeforces.com/problemset/problem/220/B 题意: 给你n个数,m次询问,每次询问问你在区间l,r内有多少个数满足其值为其出现的次数 题解: ...

  5. Codeforces 666E E - Forensic Examination SA + 莫队 + 线段树

    E - Forensic Examination 我也不知道为什么这个复杂度能过, 而且跑得还挺快, 数据比较水? 在sa上二分出上下界, 然后莫队 + 线段树维护区间众数. #include< ...

  6. CodeForces - 617E XOR and Favorite Number 莫队算法

    https://vjudge.net/problem/CodeForces-617E 题意,给你n个数ax,m个询问Ly,Ry,  问LR内有几对i,j,使得ai^...^ aj =k. 题解:第一道 ...

  7. CODEFORCES 340 XOR and Favorite Number 莫队模板题

    原来我直接学的是假的莫队 原题: Bob has a favorite number k and ai of length n. Now he asks you to answer m queries ...

  8. XOR and Favorite Number CodeForces - 617E(前缀异或+莫队)

    题意原文地址:https://blog.csdn.net/chenzhenyu123456/article/details/50574169 题意:有n个数和m次查询,每次查询区间[l, r]问满足a ...

  9. Machine Learning CodeForces - 940F(带修改的莫队)

    题解原文地址:https://www.cnblogs.com/lujiaju6555/p/8468709.html 给数组a,有两种操作,1 l r查询[l,r]中每个数出现次数的mex,注意是出现次 ...

随机推荐

  1. 初学python-day2 字符串格式化1

  2. 【UE4 C++】读写Text文件 FFileHelper

    CoreMisc.h 读取 FFileHelper::LoadFileToString 读取全部内容,存到 FString FString TextPath = FPaths::ProjectDir( ...

  3. OO第四次博客作业--第四单元总结及课程总结

    一.总结第四单元两次作业的架构设计 1.1 第一次作业 类图如下: 为了突出类.接口.方法.属性.和参数之间的层次结构关系,我为 Class 和 Interface 和 Operation 分别建立了 ...

  4. Python中Numpy及Matplotlib使用

    Python中Numpy及Matplotlib使用 1. Jupyter Notebooks 作为小白,我现在使用的python编辑器是Jupyter Notebook,非常的好用,推荐!!! 你可以 ...

  5. 全志TinaLinux编译错误fatal error: unicode/ucnv.h: No such file or directory

    今天开始正式干活了 拿到一个全志Tina的板子还有一个SDK压缩包,要求我这周(只剩一天半...)就要把sdk编译通过并且把板子跑起来. 还特别跟我说他们试了下这个sdk编译没法通过,会报错... 竟 ...

  6. 双栈排序 牛客网 程序员面试金典 C++ Python

    双栈排序 牛客网 程序员面试金典 C++ Python 题目描述 请编写一个程序,按升序对栈进行排序(即最大元素位于栈顶),要求最多只能使用一个额外的栈存放临时数据,但不得将元素复制到别的数据结构中. ...

  7. Python 字符串的encode与decode

    python的str,unicode对象的encode和decode方法 python中的str对象其实就是"8-bit string" ,字节字符串,本质上类似java中的byt ...

  8. hdu 2999 Stone Game, Why are you always there? (简单SG,有个优化)

    题意: 一排石头,个数是K. 有n个数,a1...an. 每人每次取石子只能取连续的x个.x属于a1...an的一个. 没法取者负. 思路: 简单的SG.但是TLE!后面加了一个优化~这个优化不好想到 ...

  9. 前端面试手写代码——call、apply、bind

    1 call.apply.bind 用法及对比 1.1 Function.prototype 三者都是Function原型上的方法,所有函数都能调用它们 Function.prototype.call ...

  10. 百亿级小文件存储,JuiceFS 在自动驾驶行业的最佳实践

    自动驾驶是最近几年的热门领域,专注于自动驾驶技术的创业公司.新造车企业.传统车厂都在这个领域投入了大量的资源,推动着 L4.L5 级别自动驾驶体验能尽早进入我们的日常生活. 自动驾驶技术实现的核心环节 ...