111
线性基
一 .定义
线性基是针对某个序列生成的一个集合,它具有以下两条性质:
- 线性基中任意选择一些数的异或值所构成的集合,等于原序列中任意选择一些数的异或值所构成的集合。
- 线性基是满足上述条件的最小集合。
有了上面这两条性质,我们便可以得出如下几条推论:
- 原序列中任何数,都可以由线性基中一些数异或起来得到(由性质1直接得出)
- 线性基中不存在一组数,使得它们的异或值为
0 (如果存在x\oplus y\oplus z=0 ,它就等价于x\oplus y=z ,就可以删掉z 使得异或集合不变,违背了性质2) - 线性基中不存在两组取值集合,使得它们的异或和相等(不然你把这两个集合异或在一起就会得到异或和为
0 的集合)
二. 构造
我们采取动态构造的方法。
异或线性基中,我们记
假设我们已经有了
void ins(LL x)
{
for (int i = 63;~i;i--)
{
if ((x>>i)&1)
{
if (!b[i]) return (void)(b[i] = x);
x ^= b[i];
}
}
}
我们要 尽量把
如果
如果
那如果这时的
那什么时候这个
这就是构建线性基的方式,时间复杂度
三.线性基操作
1.求最大异或和
LL ans = 0;
for (int i = 63;~i;i--) ans = max(ans,ans^b[i]);
从高位往低位枚举
显然,如果
而如果这位上是
因此最终呈现出来的结果就是异或之后答案更优就异或。
P3812 【模板】线性基
2.线性基合并
因为我们线性基是动态构造的,所以合并比较朴素,直接把其中一个线性基的数全都暴力插入到另一个线性基里就完成了。
CF1100F Ivan and Burgers
简化题意:查询区间最大异或和。
ABC223H Xor Query
P4839 P 哥的桶
简化题意:
P3292 [SCOI2016] 幸运数字
简化题意:查询树上路径的最大异或和。
P11620 [Ynoi Easy Round 2025] TEST_34
简化题意:区间异或,查询区间最大异或和。
3.线性基上排序与贪心
P4570 [BJWC2011] 元素
简化题意:给定
不能有任何一个子集异或出来为 0,可以转换为不存在任意一个数可以被集合中其他的数异或出来,其实就是线性基。
在满足线性基的性质上要求
另一个做法是不排序,插入时若线性基这一位的值的
void ins(int j)
{
for (int i = 62;~i && c[j].a;i--)
{
if((c[j].a>>i)&1)
{
if(!b[i]) return (void)(b[i] = j);
if (c[j].b > c[b[i]].b) swap(j,b[i]);
c[j].a ^= c[b[i]].a;
}
}
}
P4301 [CQOI2013] 新Nim游戏
回忆起NIM游戏先手必胜当且仅当所有石子堆的异或和不为 0。
我们可以发现,这题先手必然必胜——因为先手第一次总是可以取到只剩一堆石子,然后后手第一次就什么也取不了,然后先手第二次就可以直接取掉剩下那一堆。
从上面那个思路中,我们发现先手的目标肯定是去掉一些数,使得剩下的数不存在异或和为0的非空子集。不然,对手肯定就直接把除了那个非空子集外其它东西全部取光,先手就必败了。
不存在异或和为
同样从大到小排序后插入。
4.每个异或值出现了多少次
P3857 [TJOI2008] 彩灯
根据线性基的性质:线性基中不存在两组取值集合,使得它们的异或和相等(不然你把这两个集合异或在一起就会得到异或和为
构建线性基之后,记
P4869 albus就是要第一个出场
如果去重,可以直接从大到小扫一遍求出询问数的排名,不去重每个数重复次数为
CF959F Mahmoud and Ehab and yet another xor task
把询问离线下来。
CF895C Square Subsets
分解质因数之后等价于所有质因数的次数都为偶数,等价于异或和为
四.线性基在图论方面的应用
P4151 [WC2011] 最大XOR和路径
dfs 找出所有的环,把对应的异或和扔进线性基中,最后查询最大异或和。
CF845G Shortest Path Problem?
同上。
五.例题
P11472 命运黄之瓜
End
由于会的比较少,所以线性基有很多东西没讲,大家可以根据下方博客自行学习。
线性基学习笔记
第 k 小问题
一.静态全局第 k 小
单次询问使用 nth_element(a+1,a+k+1,a+n+1) 。
多次询问排一遍序。
二.动态全局第 k 小
平衡树,值域线段树上二分。
三.静态区间第 k 小
主席树上二分,整体二分
四.动态区间第 k 小
强制在线使用树套树:树状数组套值域线段树,树状数组套 01trie,树状数组套 vector
离线可以使用:整体二分
这里讲一下整体二分。
P2617 Dynamic Rankings
将修改拆分成先减后加,之后进行整体二分。
void solve(int l,int r,int L,int R)
{
if (l == r)
{
for (int i = L;i <= R;i++)
if (q[i].op) ans[q[i].id] = l;
return ;
}
int lc = L,rc = R,mid = (l+r)>>1;
for (int i = L;i <= R;i++)
{
if (q[i].op)
{
int k = Q(q[i].r)-Q(q[i].l-1);
if (q[i].k <= k) tmp[lc++] = q[i];
else q[i].k -= k,tmp[rc--] = q[i];
}
else
{
if (q[i].r <= mid)
A(q[i].l,q[i].k),tmp[lc++] = q[i];
else tmp[rc--] = q[i];
}
}
for (int i = L;i <= R;i++)
if (!q[i].op && q[i].r <= mid) A(q[i].l,-q[i].k);
reverse(tmp+rc+1,tmp+R+1);
for (int i = L;i <= R;i++) q[i] = tmp[i];
solve(mid+1,r,rc+1,R),solve(l,mid,L,lc-1);
}
五.第 k 小数对和
P1631 序列合并
将两个数列从小到大排序后,两两形成数对的和如下图。
维护一个优先队列,初始将每一行的第一个数放进去,每次去除队首同时将对应行下一个数放入优先队列,时间复杂度
六.第 k 小子段和
P2048 [NOI2010] 超级钢琴
区间和等于前缀和数组的差,及前缀和数组为
对于每个位置,我们把
杂项
树上邻点第 k 小,对于每个点维护一个值域线段树或者平衡树,把儿子插入进去。
树链第 k 小,树剖把树链剖成
子树第 k 小,静态可以线段树合并或者平衡树启发式合并,带修的话,拍成
数据结构再入门
前言
至于为什么备课备的这么慢,其实是准备了一些小题,因为我个人认为只把板子讲会了似乎对做题确实是没什么用处,大家可以思考一下自己听完多项式之后现在会不会写多项式题(虽然不太恰当,因为大家多项式之前确实是没涉及过)。
主要还是数据结构在你会写的同时还要学好多好多 trick 才能做题,同时大家平时对数据结构的练习和了解相较于其他算多的了,所以感觉只讲板子意一是大家学不到什么东西,二是比较敷衍,不是很负责。
因此这次课有些是对 kinna 上次讲的线段树和平衡树所涉及的一些 trick 的补充。
小验收?
P3792 由乃与大母神原型和偶像崇拜
题意:给你一个长为
每次两个操作:
-
修改
x 位置的值为y -
查询区间
[l,r] 是否可以重排为值域上连续的一段
对于
初始值的值域小于
Sol:首先析合树不支持修改,所以放弃你幼稚的想法,考虑正解。
- 一个序列能重排为值域上连续的一段,那么
\max-\min = r-l 。假如这里是排列的话,显然维护一下区间 max 和 min 就足够了,可惜不是。 - 考虑额外维护区间和。
- 多维护一个区间平方和,若平方和相等,那么大概率就是了。(随机数据下维护了平方和正确率已经非常高了,同时也就是说能构造数据卡掉,但是谁会卡这个呢?)
来个经验
P5278 算术天才⑨与等差数列
题意:一个长度为
Sol:与上题类似。
-
\max-\min = (r-l)*k -
sum1 = (\max+\min)*(r-l+1)/2 -
sum2 = \min^2*(r-l+1)+(r-l+1)*(r-l)*\min+(1^2+2^2+3^2+...)*k^2
一、可持久化数据结构
1. 可持久化数组 & 可持久化线段树
引入:P3919 【模板】可持久化线段树 1(可持久化数组)
题意:如题,你需要维护这样的一个长度为
-
在某个历史版本上修改某一个位置上的值
-
访问某个历史版本上的某一位置的值
此外,每进行一次操作(对于操作2,即为生成一个完全一样的版本,不作任何改动),就会生成一个新的版本。版本编号即为当前操作的编号(从1开始编号,版本0表示初始状态数组)
对于100%的数据:
Sol:一个非常 naive 的想法是每次把整个数组复制一遍,时空复杂度双双暴毙。
想偷懒使用 vector?显然错误的想法。
考虑把他建成一棵线段树,每次单点修改或查询时时把经过的节点复制一遍,时间空间复杂度均为
这里运用的思想就是最大程度利用历史版本的信息,当我们不得不对树的一些结构或信息进行修改时再新开节点修改,这样不会影响历史版本的信息。
想要图片?
void M(int l,int r,int pos,int k,int &i)
{
int p = i;i = ++cnt;tr[i] = tr[p];
if (l == r) {tr[i].v = k;return ;}
int mid = (l+r)>>1;
if (pos <= mid) M(l,mid,pos,k,tr[i].ls);
else M(mid+1,r,pos,k,tr[i].rs);
}
P3402 可持久化并查集
题意:
给定
有
-
1 a b合并a,b 所在集合; -
2 k回到第k 次操作(执行三种操作中的任意一种都记为一次操作)之后的状态; -
3 a b询问a,b 是否属于同一集合,如果是则输出1 ,否则输出0 。
对于
Sol:并查集如何可持久化?容易发现一次合并操作其实就是 fa 数组和 siz 数组的修改,把这两个数组可持久化即可。
P3834 【模板】可持久化线段树 2
题意:给定
- 对于
100\% 的数据,满足1 \leq n,m \leq 2\times 10^5 ,0\le a_i \leq 10^9 ,1 \leq l \leq r \leq n ,1 \leq k \leq r - l + 1 。
Sol: 我们有一个空的权值线段树,每次把询问的区间取出来,一个一个数插进去是不是就可以在线段树上二分了?是的。
那么我们把他可持久化之后,对于一次查询区间
权值线段树的话记得离散化,就算是动态开点也要离散化(有的时候卡有的时候不卡而已)。
不会线段树二分?
int Q(int a,int b,int l,int r,int k)
{
if (l == r) return l;
int siz = tr[tr[b].ls].cnt-tr[tr[a].ls].cnt;
int mid = (l+r)>>1;
if (siz >= k) return Q(tr[a].ls,tr[b].ls,l,mid,k);
else return Q(tr[a].rs,tr[b].rs,mid+1,r,k-siz);
}
P3567 [POI2014] KUR-Couriers
题意:
给一个长度为
Sol: 正常做的话肯定是不好做,但是欣喜地发现只有一个询问???
首先对于一个数 x ,我们把大于等于它的数设为 1 ,否则设为 0 ,那么进行 m 次排序后,若第 q 位为 1 ,是不是就代表着这 m 次排序过后第 q 位的数要大于 x ?然后容易发现这是不是满足单调性来着?好像是能二分答案的吧。
那么怎么处理排序?你都转化为 01 串了,直接拿线段树维护一下不就好了?
P2839 [国家集训队] middle
题意:一个长度为
给你一个长度为
回答
其中
位置也从
我会使用一些方式强制你在线。
对于
Sol:跟上道题同样的 trick ,我们二分答案这个中位数。
但是还是有些不一样的,对于一个数 x ,我们把小于它的设为 -1 ,大于等于它的设为 1 ,那么对于一个区间,如果它的和大于等于 0 ,那么是不是代表这个区间的中位数大于等于 x ?
那么对于当前二分的数 x ,我们贪心的想肯定是让区间和尽可能的大这样更容易满足和大于等于零进而二分出更大的中位数,那我我们是不是就是要求左端点在
但是还有问题,现在会二分了,也会求最大子段和了,那么对于每次二分的数 x ,总不能每次都建一遍线段树吧?这时候终于扣上题了,我们把输入的序列从小到大排好序,从左往右遍历一个一个把数的值设为 -1 ,同时保留历史版本,这不就是我们的可持久化线段树吗!!!
然后就没了,综合题,难评。
2.可持久化01trie
对于可持久化01trie在维护数值方面是全方位薄杀和持久化平衡树的,同时,它还能处理平衡树所不能处理的异或,所以不要再写维护数值的可持久化平衡树了!!!
What is 01trie?
只有 0 和 1 两个儿子的 trie 树,插入一个数时按二进制插入,他能干什么?
首先,它支持插入和删除,求前驱后继,区间第 k 小,一个数的排名。。。
那么对他可持久化之后能干什么?
先来个只有他能干的。
P4735 最大异或和
题意:
给定一个非负整数序列
有
A x:添加操作,表示在序列末尾添加一个数x ,序列的长度N 加1 。Q l r x:询问操作,你需要找到一个位置p ,满足l \le p \le r ,使得:a[p] \oplus a[p+1] \oplus ... \oplus a[N] \oplus x 最大,输出最大值。
- 对于所有测试点,
1\le N,M \le 3\times 10 ^ 5 ,0\leq a_i\leq 10 ^ 7 。
Sol:考虑维护前缀异或数组,那么对于每个询问就是找到一个位置 p 满足
P2633 Count on a tree
题意:
给定一棵
对于
Sol:
假如是序列的话大家都会做吧,序列搬到树上同理,变成树上前缀和就好了。
经验
P4592 [TJOI2018] 异或
题意:
现在有一颗以
-
-
-
对于
100\% 的数据,保证2\leq n, q \leq10^5 ,1 \leq u, v, x, y \leq n ,1 \leq op \leq 2 ,1 \leq v_i, z \lt 2^{30} 。
Sol:对于两种询问发现一个是链一个是子树,导致我们如果按 dfn 序建树的话只能处理 1 询问,按树边建树的话只能处理 2 询问,所以建两棵树就好了。、
P5795 [THUSC2015] 异或运算
题意:
给定长度为
对于
Sol:询问矩阵好像挺难的样子,能不能建一个可持久化01trie套可持久化01trie呢?不行的话每次把这个矩阵的数暴力拿出来排个序呢?难评。
容易发现
问题又来了,怎么多树二分?
多树二分其实跟一棵树上二分一样,不过是把这 n 个根的贡献加起来和 k 比较。
感觉看代码会比较好
int Q(int dep,int ans,int k)
{
if(dep < 0) return ans;
int sum = 0;
for (int i = u;i <= d;i++)
{
bool f = (x[i]>>dep)&1;
sum += s[ch[b[i]][!f]]-s[ch[a[i]][!f]];
}
for (int i = u;i <= d;i++)
{
bool f = ((x[i]>>dep)&1)^(k <= sum);
a[i] = ch[a[i]][f],b[i] = ch[b[i]][f];
}
if(k <= sum) ans += (1<<dep);
else k -= sum;
return Q(dep-1,ans,k);
}
接下来撕票可持久化平衡树。
P3835 【模板】可持久化平衡树
是不是要先写掉平衡树的板子来着
#include <bits/stdc++.h>
#define pb push_back
using namespace std;
const int N = 1e5+5;
int n,m,a[N],cnt,ch[N<<5][2],siz[N<<5],rt;
void M(int dep,int p,int k,int &i)
{
if(!i) i = ++cnt;
if(dep < 0) return (void)(siz[i] += k);
if((p>>dep)&1) M(dep-1,p,k,ch[i][1]);
else M(dep-1,p,k,ch[i][0]);
siz[i] = siz[ch[i][0]]+siz[ch[i][1]];
}
int Q(int dep,int k,int s,int i)
{
if(dep < 0) return s;
int sum = 0;
if((k>>dep)&1) return Q(dep-1,k,s+siz[ch[i][0]],ch[i][1]);
else return Q(dep-1,k,s,ch[i][0]);
}
int Q1(int dep,int k,int s,int i)
{
if(dep < 0) return s;
if(siz[ch[i][0]] >= k) return Q1(dep-1,k,s,ch[i][0]);
else return Q1(dep-1,k-siz[ch[i][0]],s+(1<<dep),ch[i][1]);
}
int main()
{
int n;cin >> n;
while(n--)
{
int op,x;cin >> op >> x;
if(op == 1) M(28,x+1e7,1,rt);
if(op == 2) M(28,x+1e7,-1,rt);
if(op == 3) cout << Q(28,x+1e7,0,rt)+1 << '\n';
if(op == 4) cout << int(Q1(28,x,0,rt)-1e7) << '\n';
if(op == 5) cout << int(Q1(28,Q(28,x+1e7,0,rt),0,rt)-1e7) << '\n';
if(op == 6) cout << int(Q1(28,Q(28,x+1e7+1,0,rt)+1,0,rt)-1e7) << '\n';
}
return 0;
}
剩下的就是修改的时候复制一遍了。
3.可持久化平衡树
可持久化平衡树专指可持久化 fhq。
至于怎么可持久化,同样是修改的时候复制一下节点就好了。
什么时候会修改?当然是 split 和 merge 。
void copy(int x,int y)
{ls[x] = ls[y],rs[x] = rs[y],siz[x] = siz[y],v[x] = v[y],rd[x] = rd[y];}
void split(int i,int k,int &x,int &y)
{
if (!i) {x = y = 0;return ;}
copy(++cnt,i);i = cnt;
if (v[i] <= k) x = i,split(rs[i],k,rs[i],y),pushup(x);
else y = i,split(ls[i],k,x,ls[i]),pushup(y);
}
int merge(int x,int y)
{
if (!x || !y) return x|y;int rt = ++cnt;
if (rd[x] < rd[y]) {copy(rt,x);rs[rt] = merge(rs[rt],y),pushup(rt);return rt;}
else {copy(rt,y);ls[rt] = merge(x,ls[rt]),pushup(rt);return rt;}
}
所以可持久化就是无脑复制节点就行了。
来个题。
T-Shirts
题意:有
有
问最后每个人买了多少件 T 恤?如果有多个
Sol:把 T 恤按优先级排遍序之后,从左往右扫,容易发现每次进行的操作就是将所有大于等于
是不是因为,每次修改减去
但是有不有一种可能,对于一次修改,我们只把那些减完之后会破坏平衡树性质的数拿出来重新插入一遍不就行了?先不考虑复杂度的话,那么究竟哪些数需要重新插入呢?
首先,
那是不是只有
那么复杂度对不对呢?
考虑一个数被修改之后数值大小至少是变为原来的一半的,因此一个数只会被插入
但是我们讲的不是可持久化平衡树吗?
所以我们先给这题来个强制在线,那么该怎么办?
连一刻都没有为平衡树的死亡哀悼,立刻赶到战场的是可持久化平衡树。
从后往前转移,因为前面的优先级比后面的高,所以肯定能买前面的先买前面的。
那么如何优化呢?
每次在前面增加衣服的时候相当于
然后你就会发现这又何尝不是值域平衡树呢(至于为什么是值域因为你 dp 数组定义的就是啊)但是值域是
我们这可是可持久化平衡树,能最大程度利用先前版本的信息,高效处理区间复制,所以我们初始开一个节点,让他自我复制个 30 次它的 siz 不久超过
Look at the code
可持久化平衡树,Amazing!
4.可持久化可并堆
可持久化不都一个套路吗,修改啥 copy 啥,同时鉴于可持久化可并堆除了写 k 短路没啥用了,所以摆了。
树套树
KDT主要解决二维平面上的矩形修改,矩形查询问题 树套树主要解决二维平面上的单点修改,矩形查询或者矩形修改单点查询问题 “CDQ分治”功能上和树套树是等价的,所以这里讲题的时候主要讲树套树做法,因为我们关注的是数据结构题的思维方法而不是具体实现细节
树套树就其实就是树的每个节点都维护一棵树。。。
1.树状数组套平衡树
P3380 【模板】树套树
题意:
-
查询
k 在区间内的排名 -
查询区间内排名为
k 的值 -
修改某一位置上的数值
-
查询
k 在区间内的前驱(前驱定义为严格小于x ,且最大的数,若不存在输出-2147483647) -
查询
k 在区间内的后继(后继定义为严格大于x ,且最小的数,若不存在输出2147483647)
对于
Sol:
上道题的弱化版,同样的
所以非必要内层不要套平衡树。
2.树状数组套01trie
所以我们迎来伟大的 01trie
P3380 【模板】树套树
对于 3 操作:同平衡树。
对于 1 操作:同平衡树。
对于 2 操作:
对于区间限制,我们拆成两个区间
对于这两个区间,各拆成
左儿子个数小于 k 就往右走,否则往左走。
对于4,5操作:同平衡树。
现在你已经能切掉它了。
#include <bits/stdc++.h>
#define pb push_back
using namespace std;
const int N = 5e4+5;
int n,m,a[N],cnt,ch[N<<9][2],siz[N<<9],rt[N];
void add(int dep,int p,int k,int &i)
{
if(!i) i = ++cnt;
if(dep < 0) return (void)(siz[i] += k);
if((p>>dep)&1) add(dep-1,p,k,ch[i][1]);
else add(dep-1,p,k,ch[i][0]);
siz[i] = siz[ch[i][0]]+siz[ch[i][1]];
}
//修改
void M(int i,int a,int b)
{
while(i <= n)
{
if(~a) add(31,a,-1,rt[i]);
add(31,b,1,rt[i]);
i += i&-i;
}
}
vector < int > t1,t2;
int A(int dep,int k,int s)
{
if(dep < 0) return s;
int sum = 0;
for (int i : t1) sum += siz[ch[i][0]];
for (int i : t2) sum -= siz[ch[i][0]];
if(sum < k)
{
for (int i = 0;i < t1.size();i++) t1[i] = ch[t1[i]][1];
for (int i = 0;i < t2.size();i++) t2[i] = ch[t2[i]][1];
return A(dep-1,k-sum,s+(1<<dep));
}
else
{
for (int i = 0;i < t1.size();i++) t1[i] = ch[t1[i]][0];
for (int i = 0;i < t2.size();i++) t2[i] = ch[t2[i]][0];
return A(dep-1,k,s);
}
}
int Q2(int dep,int k,int s,int i)
{
if(dep < 0) return s;
int sum = 0;
if((k>>dep)&1) return Q2(dep-1,k,s+siz[ch[i][0]],ch[i][1]);
else return Q2(dep-1,k,s,ch[i][0]);
}
//排名
int Q1(int l,int r,int k)
{
int sum = 0;
while(r) sum += Q2(31,k,0,rt[r]),r -= r&-r;
while(l) sum -= Q2(31,k,0,rt[l]),l -= l&-l;
return sum;
}
//第 k 小
int Q(int l,int r,int k)
{
t1.clear(),t2.clear();
while(r) t1.pb(rt[r]),r -= r&-r;
while(l) t2.pb(rt[l]),l -= l&-l;
return A(31,k,0);
}
int main()
{
cin >> n >> m;
for (int i = 1;i <= n;i++)
cin >> a[i],M(i,-1,a[i]);
for (int i = 1;i <= m;i++)
{
int op,l,r,x;cin >> op >> l >> r;
if(op == 1) cin >> x,cout << Q1(l-1,r,x)+1 << '\n';
if(op == 2) cin >> x,cout << Q(l-1,r,x) << '\n';
if(op == 3) M(l,a[l],r),a[l] = r;
if(op == 4)
{
cin >> x;
int rk = Q1(l-1,r,x);
if(!rk) cout << "-2147483647" << '\n';
else cout << Q(l-1,r,rk) << '\n';
}
if(op == 5)
{
cin >> x;
int rk = Q1(l-1,r,x+1);
if(rk == r-l+1) cout << "2147483647" << '\n';
else cout << Q(l-1,r,rk+1) << '\n';
}
}
return 0;
}
P2617 Dynamic Rankings
换成 01trie 就好了。
3.树状数组套vector
众所周知随机数据下 vector 是能过平衡树板题的。
大家应该都会。但还是看一下吧。
#include <bits/stdc++.h>
using namespace std;
vector< int >v;
int main()
{
int n;scanf("%d",&n);
for(int i = 1;i <= n;i++)
{
int op,x;scanf("%d%d",&op,&x);
if(op == 1) v.insert(lower_bound(v.begin(),v.end(),x),x);
if(op == 2) v.erase(lower_bound(v.begin(),v.end(),x));
if(op == 3) printf("%d\n",lower_bound(v.begin(),v.end(),x)-v.begin()+1);
if(op == 4) printf("%d\n",v[x-1]);
if(op == 5) printf("%d\n",v[lower_bound(v.begin(),v.end(),x)-v.begin()-1]);
if(op == 6) printf("%d\n",v[upper_bound(v.begin(),v.end(),x)-v.begin()]);
}
return 0;
}
P2617 Dynamic Rankings
回到这个题。
把 fhq 换成vector,然后你就过了。
int n,m,a[N];
vector < int > v[N];
int Q(int l,int r,int k)
{
int sum = 0;
while(r) sum += upper_bound(v[r].begin(),v[r].end(),k)-v[r].begin(),r -= r&-r;
while(l) sum -= upper_bound(v[l].begin(),v[l].end(),k)-v[l].begin(),l -= l&-l;
return sum;
}
void add(int i,int a,int b)
{
while(i <= n)
{
if(~a) v[i].erase(lower_bound(v[i].begin(),v[i].end(),a));
v[i].insert(lower_bound(v[i].begin(),v[i].end(),b),b);
i += i&-i;
}
}
这时候就能发现有时不是
P3380 【模板】树套树
跑的比 01trie 还快。
上为 01trie,下为 vector
看这优秀的空间,知道改练谁了吧。
4.树状数组套线段树
P2617 Dynamic Rankings
还是这个题。
静态区间第 k 小大家都会写主席树。
那这个题不就是动态区间第 k 小吗?
静态区间求和是不是开个前缀和数组就行了?
动态区间求和是不是用树状数组维护一下就行了?
主席树不就相当于开了个前缀和数组方便快速求出区间信息吗?
那么把这个前缀和数组换成树状数组不就能维护动态区间信息吗?
大家应该能理解主席树就是数组套线段树吧。
说到这里大家应该已经听懂了吧。
操作基本没啥变化。
他们把这个称作动态主席树。
P3157 [CQOI2011] 动态逆序对
题意:
对于序列
现在给出
Sol:
初始的逆序对数大家应该都会归并或树状数组吧?
考虑求出删数会减少的逆序对数减一下就是剩下的逆序对数。
怎么求减少的逆序对数呢?
假如我们要删
这不就是查询区间内大于他或小于他的数的个数同时还带个修吗?
``Dynamic'' Inversion 喜闻乐见的经验。
P1975 [国家集训队] 排队
题意:
排排坐,吃果果,生果甜嗦嗦,大家笑呵呵。你一个,我一个,大的分给你,小的留给我,吃完果果唱支歌,大家乐和和。
红星幼儿园的小朋友们排起了长长地队伍,准备吃果果。不过因为小朋友们的身高有所区别,排成的队伍高低错乱,极不美观。设第
幼儿园阿姨每次会选出两个小朋友,交换他们的位置,请你帮忙计算出每次交换后,序列的逆序对数。为方便幼儿园阿姨统计,在未进行任何交换操作时,你也应该输出该序列的逆序对数。
对于100%的数据,
Sol:
思路和上道题一模一样,但是交换造成的逆序对数变化不同。
交换 x 位置和 y 位置的数,逆序对数加上
P3759 [TJOI2017] 不勤劳的图书管理员:
贡献变为了页数和,在记录个数的时候额外记录一下页数和就好。
其实树套树还能处理好多偏序问题,出于篇幅原因请自行查阅。