当前位置: 首页 > news >正文

浅谈压位 trie 及其简单应用

Update on 2026.1.23:修改了一些错误。

突然想写就写了。

功能

可以在 \(O(\log_w{V})\)\(w\) 为计算机位长,通常取 \(32\)\(64\))的时间复杂度、\(O(\frac{V}{w})\) 的空间复杂度下完成以下操作:

  • 插入一个不存在的数或删除一个数;

  • 查询最小或最大值;

  • 查询前驱后继。

正常用 01trie 只能做到 \(O(\log_2{V})\),因为它是一个 \(2\) 叉树。我们发现每个位置只用存 \(0/1\),我们直接分 \(w\) 叉不就优完了?

实现

插入删除是好做的,查询最小最大值的话用 __builtin_ctz__builtin_clz 稍微处理一下就可以了。查询前驱后继的话,跟正常的平衡树一样:我们从叶子结点往根跑,查询一下有没有比它大或小的兄弟,然后再在这里面查询最小或最大值即可。

以下是对于 \(V=2^{20}\) 的压位 trie 代码(直接展开写的,常数会小一些):

struct trie{#define ctz(x) __builtin_ctz(x)#define clz(x) __builtin_clz(x)int ans;unsigned int a0,a1[1<<5],a2[1<<10],a3[1<<15],ak;inline void ins(const int x)noexcept{a0|=1u<<(x>>15),a1[x>>15]|=1u<<((x>>10)&31),a2[x>>10]|=1u<<((x>>5)&31),a3[x>>5]|=1u<<(x&31);}inline void era(const int x)noexcept{(a3[x>>5]&=~(1u<<(x&31)))?(1):((a2[x>>10]&=~(1u<<((x>>5)&31)))?(1):((a1[x>>15]&=~(1u<<((x>>10)&31)))?(1):(a0&=~(1u<<(x>>15)))));}inline bool have(const int x)noexcept{return (a3[x>>5]>>(x&31))&1;}inline int minn()noexcept{return a0?(ans=ctz(a0),ans=(ans<<5)|ctz(a1[ans]),ans=(ans<<5)|ctz(a2[ans]),(ans<<5)|ctz(a3[ans])):(1<<20);}inline int maxn()noexcept{return a0?(ans=31^clz(a0),ans=(ans<<5)|(31^clz(a1[ans])),ans=(ans<<5)|(31^clz(a2[ans])),(ans<<5)|(31^clz(a3[ans]))):(0);}inline int suf(const int x)noexcept{if((ak=(a3[x>>5]>>(x&31))>>1)){return(x&~31u)|((x&31)+1+ctz(ak));}if((ak=(a2[x>>10]>>((x>>5)&31))>>1)){return ans=((x>>5)&~31u)|(((x>>5)&31)+1+ctz(ak)),(ans<<5)|ctz(a3[ans]);}if((ak=(a1[x>>15]>>((x>>10)&31))>>1)){return ans=((x>>10)&~31u)|(((x>>10)&31)+1+ctz(ak)),ans=(ans<<5)|ctz(a2[ans]),(ans<<5)|ctz(a3[ans]);}if((ak=a0>>(x>>15)>>1)){return ans=((x>>15)&~31u)|(((x>>15)&31)+1+ctz(ak)),ans=(ans<<5)|ctz(a1[ans]),ans=(ans<<5)|ctz(a2[ans]),(ans<<5)|ctz(a3[ans]);}return x;}inline int pre(const int x)noexcept{if((ak=a3[x>>5]&((1u<<(x&31))-1))){return(x&~31u)|(31^clz(ak));}if((ak=a2[x>>10]&((1u<<((x>>5)&31))-1))){return ans=((x>>5)&~31u)|(31^clz(ak)),(ans<<5)|(31^clz(a3[ans]));}if((ak=a1[x>>15]&((1u<<((x>>10)&31))-1))){return ans=((x>>10)&~31u)|(31^clz(ak)),ans=(ans<<5)|(31^clz(a2[ans])),(ans<<5)|(31^clz(a3[ans]));}if((ak=a0&((1u<<(x>>15))-1))){return ans=((x>>15)&~31u)|(31^clz(ak)),ans=(ans<<5)|(31^clz(a1[ans])),ans=(ans<<5)|(31^clz(a2[ans])),(ans<<5)|(31^clz(a3[ans]));}return x;}
};

动态开点的话就把上面的数组改成 unordered_map 即可,空间复杂度就是 \(\min(n\log_wV,\frac{V}{w})\)

对比

可能很多人对 \(O(\log_{w}{V})\) 没什么概念,其实就是 \(O(\log_{2}{V})\) 优化了 \(\frac{1}{5}\)\(\frac{1}{6}\),通常可以看成一个 \(4\)\(5\) 的常数。

应用

由于题目中往往可以有重复元素,所以使用压位 trie 前经常得用离散化。

以 P3378 【模板】堆 为例,可以先用基数排序让每个值互不相同然后再用压位 trie 做到 \(O(n\log_{w}n)\)

trie t;
unsigned bucket[256];
#define SORTBYTE(TYPE, FR, TO, LEN, BIT) {\memset(bucket, 0, sizeof(bucket));\for (TYPE *it = (FR) + LEN; it != (FR); it--)\++bucket[(it[-1].x >> BIT) & 255];\for (unsigned *it = bucket; it != bucket + 255; it++)\it[1] += it[0];\for (TYPE *it = (FR) + LEN; it != (FR); it--)\(TO)[--bucket[(it[-1].x >> BIT) & 255]] = it[-1];\
}
constexpr int N=1e6+5;
struct node{int id,x;}q[N],st[N],st2[N];int n,top;
int main(){cin>>n;for(int i=1;i<=n;++i)cin>>q[i].id,(q[i].id==1)?(cin>>q[i].x,st2[top++]={i,q[i].x}):(node{});SORTBYTE(node,st2,st,top, 0);SORTBYTE(node,st,st2,top, 8);SORTBYTE(node,st2,st,top,16);SORTBYTE(node,st,st2,top,24);SORTBYTE(node,st2,st,top,30);for(int i=0;i<top;++i)q[st[i].id].x=i;for(int i=1;i<=n;++i){switch(q[i].id){case 1:t.ins(q[i].x);break;case 2:cout<<st[t.minn()].x<<'\n';break;case 3:t.era(t.minn());break;}}return 0;
}

跑得有点慢,用了 \(200\) 多毫秒。

经常很多分块题你只能想到甚至只能做到类似于 \(O(n\sqrt{n\log_2n})\) 的东西,用上压位 trie 你就可以强行创过去或抢到最优解(例如 P8078 [WC2022] 秃子酋长 就可以拿 \(n\sqrt{n}\log_wn\) 应创,并且卡不掉)。

众所周知,正常的珂朵莉树区间赋值单点查用 set 只能在非随机数据下做到 \(O(n\log_2n)\),但是不难发现这其实每次都是在插入删除或求前驱后继,直接上压位 trie 就可以做到 \(O(n\log_{w}n)\)(使用 Veb 可以做到严格 \(O(n\log_2\log_2n)\))。

以 P13983数列分块入门8 为例,当其他人在使用 \(O(n\log_{2}n)\) 的 set 维护珂朵莉树时,你可以直接悄悄地交一发 \(O(n\log_{w}n)\) 的压位 trie 并成功得到最优解。

trie t;
constexpr int N=3e5+5;
int n,m;
int nxt[N],val[N],ans;
inline void split(int l)
{if(t.have(l))return;int pr=t.pre(l);t.ins(l);nxt[l]=nxt[pr],val[l]=val[pr],nxt[pr]=l;
}
int main(){cin>>n;t.ins(n+1);for(int i=1;i<=n;++i)nxt[i]=i+1,cin>>val[i],t.ins(i);int l,r,v,ll,su;for(int i=1;i<=n;++i){cin>>l>>r>>v;ans=0;split(ll=l),split(++r);do{(val[ll]==v)?(su=nxt[ll],t.era(su),ans+=su-ll,ll=su):(su=nxt[ll],t.era(su),ll=su);}while(ll!=r);nxt[l]=r,val[l]=v,t.ins(r);cout<<ans<<'\n';}return 0;
}

还有就是:两个 01trie 进行位运算的复杂度跟 bitset 复杂度是一样的,例题待补。

http://www.jsqmd.com/news/289477/

相关文章:

  • 同样能做采集控制,ARM边缘AI控制器与PLC究竟区别在哪里?
  • 2026年目前优秀的纹路袋制造厂排行榜,自立袋/三边封拉链袋/聚酯尼龙袋/中封袋/纹路袋/自立拉链袋,纹路袋制造商如何选
  • 2026年,银川哪家化妆学校靠谱?化妆培训,彩妆培训,美业培训,深度指南帮你精准避坑
  • 【SQL注入防护】避开这些坑!程序员必知的5种参数化查询与代码安全实践
  • 人事考试安全风险点防控管理信息系统
  • 开发超市囤货最优解程序,输入常买商品,保质期。家庭月消耗量,结合超市促销信息,计算囤货数量和最佳囤货时间,避免过期浪费。
  • 10款AI论文写作工具,满足数学建模论文复现与排版需求
  • 开发拼单凑单计算器,输入商品单价,满减门槛,拼单人数,自动计算每人需付金额,最优凑单商品,避免为凑单多买无用物品。
  • 中国采招网API
  • 数学建模论文如何高效复现?10个AI写作工具助你一臂之力
  • 10个AI工具帮你轻松完成数学建模论文的复现与排版
  • 数学建模论文如何高效排版?10个AI写作工具值得一试
  • 10个AI论文写作工具盘点,适用于数学建模论文复现与排版
  • 10款AI论文写作工具,优化数学建模论文的复现与排版流程
  • 数学建模论文复现困难?10款AI写作工具帮你轻松搞定
  • 《AI元人文:悟空而行——智能时代的价值决断与合法性重建》的参考文献
  • RHCSA结课考试
  • 地道螺蛳粉加盟品牌怎么选择,这些要点要知道
  • Java毕设选题推荐:基于springboot的交通安全案例知识学习网站【附源码、mysql、文档、调试+代码讲解+全bao等】
  • 计算机Java毕设实战-基于springboot+vue的交通安全知识学习平台【完整源码+LW+部署说明+演示视频,全bao一条龙等】
  • KMP 算法
  • 详细介绍:跨端一致性与体验统一:构建面向全场景的 Flutter UI 自适应架构
  • 代码中接收命令行参数,通过jenkins部署时传入不同的环境命令行参数--针对代码在不同环境下运行
  • 衡阳国家高新技术产业开发衡山科学城英语雅思培训辅导机构推荐;2026权威出国雅思课程中心学校口碑排行榜
  • P3781 [SDOI2017] 切树游戏
  • 2026年苏州门窗厂家深度选型指南:如何为你的装修需求匹配最佳方案?
  • Google Gemini系列:多模态AI的迭代演进与前沿应用
  • 邵阳双清大祥北塔邵东武冈英语雅思培训辅导机构推荐,2026权威出国雅思课程中心学校口碑排行榜
  • 100V8A_HN0801雾化器加湿器MOS管关键特性
  • Java毕设项目:基于springboot的家庭物品收纳管理系统(源码+文档,讲解、调试运行,定制等)