论可持久化Trie树贪心在区间最大最小值的运用
2017-09-28 16:30
260 查看
可持久化01Trie树主要解决的是最大异或和问题, 譬如BZOJ3261,BZOJ2741,
都是01Trie树的最大异或和的运用. 在做01Trie的时候我们贪心的尽量选与当前这一位不一样的使得高位异或下来最大. 而同样的, 我们可以建01Trie来解决区间最大最小值问题, 对于序列上每个点建一个可持久化Trie, 第i点上的01Trie就维护1 ~ i 的每个数, 把每个数二进制拆分插进01trie里. 每次询问l, r的时候就查询l-1的trie树和r的trie树, 从根开始往下, 如果当前可以选1的话就选1(选1的条件就是经过r的trie树这个点之后的数比l-1的多, 这就说明l-r的区间里有这个数,
那么就可以选, 这与区间第k大值的域线段树相减同理). 这样贪心的去选最后就一定是最大的.
综上策略, 我们就可以得到nlog的算法, 而且这个log是值域的log(我们是把每个数二进制拆分插进01trie里). 也就是说当n特别大, 但是值域很小的时候, 我们可以得到一个时间复杂度碾压线段树, 空间复杂度碾压st表的算法. 譬如n是1e9, 但值域只有1000, 那么线段树的理论复杂度就是01trie的3倍, 当然1e9的n的空间已经是难以接受的了, 这里只是举一个例子.
下面是求区间最大值的代码, 值域在1000左右.
#include<stdio.h>
const int maxbit = 10;
const int maxn = 1000005;
int id, n, q, wanna, ed;
int c[maxn * maxbit][2], tr[maxn], sum[maxn * maxbit];
inline const int read(){
register int x = 0;
register char ch = getchar();
while(ch < '0' || ch > '9') ch = getchar();
while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + ch - '0'; ch = getchar();}
return x;
}
void insert(int &rt,int pre,int who,int pos){
rt = ++id;
c[rt][0] = c[pre][0]; c[rt][1] = c[pre][1];
(!sum[rt]) ? sum[rt] = sum[pre] + 1 : sum[rt]++;
if(pos == -1) return;
wanna = (who >> pos) & 1;
insert(c[rt][wanna], c[pre][wanna], who, pos - 1);
}
inline int query(int l,int r, int opt){
ed = 0;
for(int i = maxbit; i >= 0 ; --i){
if(sum[c[r][1]] > sum[c[l][1]]) ed |= (1 << i), l = c[l][1], r = c[r][1];
else l = c[l][0], r = c[r][0];
}
return ed;
}
int main(){
n = read(), q = read();
int opt, x, y;
for(register int i = 1; i <= n; ++i) x = read(), insert(tr[i], tr[i - 1], x, maxbit);
for(register int i = 1; i <= q; ++i) printf("%d\n", query(tr[x - 1], tr[y], opt));
return 0;
}
都是01Trie树的最大异或和的运用. 在做01Trie的时候我们贪心的尽量选与当前这一位不一样的使得高位异或下来最大. 而同样的, 我们可以建01Trie来解决区间最大最小值问题, 对于序列上每个点建一个可持久化Trie, 第i点上的01Trie就维护1 ~ i 的每个数, 把每个数二进制拆分插进01trie里. 每次询问l, r的时候就查询l-1的trie树和r的trie树, 从根开始往下, 如果当前可以选1的话就选1(选1的条件就是经过r的trie树这个点之后的数比l-1的多, 这就说明l-r的区间里有这个数,
那么就可以选, 这与区间第k大值的域线段树相减同理). 这样贪心的去选最后就一定是最大的.
综上策略, 我们就可以得到nlog的算法, 而且这个log是值域的log(我们是把每个数二进制拆分插进01trie里). 也就是说当n特别大, 但是值域很小的时候, 我们可以得到一个时间复杂度碾压线段树, 空间复杂度碾压st表的算法. 譬如n是1e9, 但值域只有1000, 那么线段树的理论复杂度就是01trie的3倍, 当然1e9的n的空间已经是难以接受的了, 这里只是举一个例子.
下面是求区间最大值的代码, 值域在1000左右.
#include<stdio.h>
const int maxbit = 10;
const int maxn = 1000005;
int id, n, q, wanna, ed;
int c[maxn * maxbit][2], tr[maxn], sum[maxn * maxbit];
inline const int read(){
register int x = 0;
register char ch = getchar();
while(ch < '0' || ch > '9') ch = getchar();
while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + ch - '0'; ch = getchar();}
return x;
}
void insert(int &rt,int pre,int who,int pos){
rt = ++id;
c[rt][0] = c[pre][0]; c[rt][1] = c[pre][1];
(!sum[rt]) ? sum[rt] = sum[pre] + 1 : sum[rt]++;
if(pos == -1) return;
wanna = (who >> pos) & 1;
insert(c[rt][wanna], c[pre][wanna], who, pos - 1);
}
inline int query(int l,int r, int opt){
ed = 0;
for(int i = maxbit; i >= 0 ; --i){
if(sum[c[r][1]] > sum[c[l][1]]) ed |= (1 << i), l = c[l][1], r = c[r][1];
else l = c[l][0], r = c[r][0];
}
return ed;
}
int main(){
n = read(), q = read();
int opt, x, y;
for(register int i = 1; i <= n; ++i) x = read(), insert(tr[i], tr[i - 1], x, maxbit);
for(register int i = 1; i <= q; ++i) printf("%d\n", query(tr[x - 1], tr[y], opt));
return 0;
}
相关文章推荐
- STL有限队列的灵活运用+IO优化+区间最大最小——POJ Sliding Window
- 【bzoj3261】【最大异或和】可持久化trie树+贪心
- 运用指针输出最大值和最小值
- bzoj3261 最大异或和【可持久化trie树】
- HDU 6000 Wash【优先队列优化贪心】【最大值+最小值】
- nyoj+区间贪心中最大区间不相交数目+右端点相同并没有去讨论左端点
- BZOJ 3790 浅谈MANACHER算法+求区间最小覆盖集经典贪心
- 【bzoj3261】最大异或和 可持久化Trie树
- 将[1,n^2]区间内n^2个数字分别填充到n*n的矩阵里,要求任意两个相邻的数字的和,它们的最大值最小是多少?
- HDU 3374 String Problem(最大最小表示法模板+KMP+next数组的运用)
- HDU 1050 Moving Tables (贪心 区间最大叠加数)
- 【HDU5586 BestCoder Round 64 (div1)A】【贪心 最大连续子串】Sum 区间函数值变换使得数列权值和最大
- UVA 1331 Minimax Triangulation 最大面积最小的三角剖分(区间dp--记忆化搜索)
- BZOJ 3261 最大异或和 可持久化Trie树
- 贪心算法——n个数连接得到最小或最大的多位整数
- POJ 3264(线段树求区间内最大值和最小值)
- 线段树区间修改 懒惰标记 维护和、最大值、最小值
- poj 2823 Sliding Window (线段树 求固定区间的最大最小值 )
- 【ZOJ3919 2016年浙大2月月赛E】【简单计算几何 贪心】Ellipse 椭圆内切圆外切平行四边形最大最小面积
- 贪心法求树的最小支配集,最小点覆盖,最大独立集