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,注意是出现次 ...
随机推荐
- Convolutional Neural Network-week1编程题(一步步搭建CNN模型)
Convolutional Neural Networks: Step by Step implement convolutional (CONV) and pooling (POOL) layers ...
- Java:TreeMap类小记
Java:TreeMap类小记 对 Java 中的 TreeMap类,做一个微不足道的小小小小记 概述 前言:之前已经小小分析了一波 HashMap类.HashTable类.ConcurrentHas ...
- Asp.net Core C#进行筛选、过滤、使用PredicateBuilder进行动态拼接lamdba表达式树并用作条件精准查询,模糊查询
在asp.net core.asp.net 中做where条件过滤筛选的时候写的长而繁琐不利于维护,用PredicateBuilder进行筛选.过滤.LInq配合Ef.core进行动态拼接lamdba ...
- 2021.8.6考试总结[NOIP模拟32]
T1 smooth 考场上水个了优先队列多带个$log$,前$80$分的点跑的飞快,后面直接萎了. 其实只需开$B$个队列,每次向对应队列中插入新的光滑数,就能保证队列中的数是单调的. 为了保证不重, ...
- 反转单词顺序列 牛客网 剑指Offer
反转单词顺序列 牛客网 剑指Offer 题目描述 牛客最近来了一个新员工Fish,每天早晨总是会拿着一本英文杂志,写些句子在本子上.同事Cat对Fish写的内容颇感兴趣,有一天他向Fish借来翻看,但 ...
- JAVA笔记12__字节、字符缓冲流/打印流/对象流/
/** * !!:以后写流的时候一定要加入缓冲!! * 对文件或其它目标频繁的读写操作,效率低,性能差. * 缓冲流:好处是能更高效地读写信息,原理是将数据先缓冲起来,然后一起写入或读取出来. * * ...
- 面试官:JavaScript如何实现数组拍平(扁平化)方法?
面试官:JavaScript如何实现数组拍平(扁平化)方法? 1 什么叫数组拍平? 概念很简单,意思是将一个"多维"数组降维,比如: // 原数组是一个"三维" ...
- 20191310李烨龙作业:MySort
作业:MySort 任务详情 1. 用man sort 查看sort的帮助文档 2. sort常用选项有哪些,都有什么功能?提交相关使用的截图 3. 如果让你编写sort,你怎么实现?写出伪代码和相关 ...
- 干货分享之spring框架源码分析02-(对象创建or生命周期)
记录并分享一下本人学习spring源码的过程,有什么问题或者补充会持续更新.欢迎大家指正! 环境: spring5.X + idea 之前分析了Spring读取xml文件的所有信息封装成beanDef ...
- SpringMVC配置版到注解版
什么是springmvc? 1.1.什么是MVC MVC是模型(Model).视图(View).控制器(Controller)的简写,是一种软件设计规范. 是将业务逻辑.数据.显示分离的方法来组织代码 ...