Codeforces 700D - Huffman Coding on Segment(莫队+根分)
好家伙,刚拿到此题时我连啥是 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(莫队+根分)的更多相关文章
- 【CodeForces】700 D. Huffman Coding on Segment 哈夫曼树+莫队+分块
[题目]D. Huffman Coding on Segment [题意]给定n个数字,m次询问区间[l,r]的数字的哈夫曼编码总长.1<=n,m,ai<=10^5. [算法]哈夫曼树+莫 ...
- Codeforces 351D Jeff and Removing Periods(莫队+区间等差数列更新)
题目链接:http://codeforces.com/problemset/problem/351/D 题目大意:有n个数,每次可以删除掉数值相同并且所在位置成等差数列的数(只删2个数或者只删1个数应 ...
- Codeforces 617E XOR and Favorite Number莫队
http://codeforces.com/contest/617/problem/E 题意:给出q个查询,每次询问区间内连续异或值为k的有几种情况. 思路:没有区间修改,而且扩展端点,减小端点在前缀 ...
- codeforces 220B . Little Elephant and Array 莫队+离散化
传送门:https://codeforces.com/problemset/problem/220/B 题意: 给你n个数,m次询问,每次询问问你在区间l,r内有多少个数满足其值为其出现的次数 题解: ...
- Codeforces 666E E - Forensic Examination SA + 莫队 + 线段树
E - Forensic Examination 我也不知道为什么这个复杂度能过, 而且跑得还挺快, 数据比较水? 在sa上二分出上下界, 然后莫队 + 线段树维护区间众数. #include< ...
- CodeForces - 617E XOR and Favorite Number 莫队算法
https://vjudge.net/problem/CodeForces-617E 题意,给你n个数ax,m个询问Ly,Ry, 问LR内有几对i,j,使得ai^...^ aj =k. 题解:第一道 ...
- 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 ...
- XOR and Favorite Number CodeForces - 617E(前缀异或+莫队)
题意原文地址:https://blog.csdn.net/chenzhenyu123456/article/details/50574169 题意:有n个数和m次查询,每次查询区间[l, r]问满足a ...
- Machine Learning CodeForces - 940F(带修改的莫队)
题解原文地址:https://www.cnblogs.com/lujiaju6555/p/8468709.html 给数组a,有两种操作,1 l r查询[l,r]中每个数出现次数的mex,注意是出现次 ...
随机推荐
- Hive中的4种Join方式
common join 普通join,性能较差,存在Shuffle map join 适用情况:大表join小表时,做不等值join 原理:将小表数据广播到各个节点,存储在内存中,在map阶段直接jo ...
- airtest keyevent 按键速查表
- rabbitmq生产者消息确认
在使用 RabbitMQ 的时候,有时候当我们生产者发送一条消息到 RabbitMQ 服务器后,我们 生产者想知道消息是否到达了 RabbitMQ 服务器上.这个时候我们应该如何处理? 针对上述问题, ...
- 学习手册 | MySQL篇 · 其一
InnoDB关键特性 插入缓冲(Insert Buffer) 问题: 在InnoDB插入的时候,由于记录通常都是按照插入顺序,也就是主键的顺序进行插入的,因此,插入聚集索引是顺序的,不需要随机IO ...
- 2021.6.29考试总结[NOIP模拟10]
T1 入阵曲 二位前缀和暴力n4可以拿60. 观察到维护前缀和时模k意义下余数一样的前缀和相减后一定被k整除,前缀和维护模数,n2枚举行数,n枚举列, 开一个桶记录模数出现个数,每枚举到该模数就加上它 ...
- 网络摄像机中的IR-CUT详解
自然界存在着各种波长的光线,通过折射人眼能看到不同颜色的光线,这就是光线的波长不同所导致的.其实还有许多光线是人眼看不到的,人眼识别光线的波长范围在320nm-760nm之间,超过760nm的光线人眼 ...
- STM32 禁用或开启总中断
今天把之前自己的一些在中断方面所产生的疑惑把具体的解决办法给大家分享一下,希望能够帮到大家. STM32在使用时有时需要禁用全局中断,比如MCU在升级过程中需禁用外部中断,防止升级过程中外部中断触发导 ...
- 零基础学习C语言字符串操作总结大全
本篇文章是对C语言字符串操作进行了详细的总结分析,需要的朋友参考下 1)字符串操作 strcpy(p, p1) 复制字符串 strncpy(p, p1, n) 复制指定长度字符串 strcat(p, ...
- 洛谷 P6075 [JSOI2015]子集选取
链接:P6075 前言: 虽然其他大佬们的走分界线的方法比我巧妙多了,但还是提供一种思路. 题意: %&¥--@#直接看题面理解罢. 分析过程: 看到这样的题面我脑里第一反应就是DP,但是看到 ...
- 2021 ICPC Gran Premio de Mexico 2da Fecha部分题题解
前面的水题,在队友的配合下,很快就拿下了,剩下几道大毒瘤题,一直罚座三个小时,好让人自闭...但不得不说,这些题的质量是真的高! H. Haunted House 首先看这个题,大眼一扫,觉得是某种数 ...