ACM-数据结构完全总结(知识点+模板)
目录
- STL中数据结构通用操作
1.1二分查找
1.2排列生成 - 栈
2.1单调栈 - 队列
3.1优先队列
3.2单调队列 - 向量
- 链表
5.1链式前向星
5.2舞蹈链(dancing links) - 堆
6.1映射二叉堆 - 集合
- 映射
- ST表
- 并查集
*10.1带权并查集
*10.2种类并查集
*10.3可持久化并查集 - 树状数组
- 线段树
12.1 ZKW线段树
*12.2 权值线段树
12.3 可持久化线段树(主席树) - 平衡树
13.1 Splay伸展树
13.2 Treap树堆
13.3 替罪羊树(重量平衡树) - 珂朵莉树(ODT)
- KD树(kd-tree)
- Trie字典树
16.1自动机 - LCT(动态森林)
- 序列
18.1 dfs序
18.2 欧拉序 - 树链剖分
- 根号算法
20.1分块算法
20.2莫队算法 - CDQ分治
21.1三维偏序
21.2四维偏序
注:由于数据结构过于繁杂,部分*内容后续会慢慢补齐,持续更新中…
一.STL中数据结构常见操作
1. a.assign(b.begin(), b.begin()+3); //b为向量,将b的0~2个元素构成的向量赋给a
2. a.assign(4,2); //是a只含4个元素,且每个元素为2
3. a.back(); //返回a的最后一个元素
4. a.front(); //返回a的第一个元素
5. a[i]; //返回a的第i个元素,当且仅当a[i]存在2013-12-07
6. a.clear(); //清空a中的元素
7. a.empty(); //判断a是否为空,空则返回ture,不空则返回false
8. a.pop_back(); //删除a向量的最后一个元素
9. a.erase(a.begin()+1,a.begin()+3); //删除a中第1个(从第0个算起)到第2个元素,也就是说删除的元素从a.begin()+1算起(包括它)一直到a.begin()+ 3(不包括它)
10. a.push_back(5); //在a的最后一个向量后插入一个元素,其值为5
11. a.insert(a.begin()+1,5); //在a的第1个元素(从第0个算起)的位置插入数值5,如a为1,2,3,4,插入元素后为1,5,2,3,4
12. a.insert(a.begin()+1,3,5); //在a的第1个元素(从第0个算起)的位置插入3个数,其值都为5
13. a.insert(a.begin()+1,b+3,b+6); //b为数组,在a的第1个元素(从第0个算起)的位置插入b的第3个元素到第5个元素(不包括b+6),如b为1,2,3,4,5,9,8 ,插入元素后为1,4,5,9,2,3,4,5,9,8
14. a.size(); //返回a中元素的个数;
15. a.capacity(); //返回a在内存中总共可以容纳的元素个数
16. a.resize(10); //将a的现有元素个数调至10个,多则删,少则补,其值随机
17. a.resize(10,2); //将a的现有元素个数调至10个,多则删,少则补,其值为2
18. a.reserve(100); //将a的容量(capacity)扩充至100,也就是说现在测试a.capacity();的时候返回值是100.这种操作只有在需要给a添加大量数据的时候才 显得有意义,因为这将避免内存多次容量扩充操作(当a的容量不足时电脑会自动扩容,当然这必然降低性能)
19. a.swap(b); //b为向量,将a中的元素和b中的元素进行整体性交换
20. a==b; //b为向量,向量的比较操作还有!=,>=,<=,>,<
21. sort(a.begin(),a.end()); //对a中的从a.begin()(包括它)到a.end()(不包括它)的元素进行从小到大排列
22. reverse(a.begin(),a.end()); //对a中的从a.begin()(包括它)到a.end()(不包括它)的元素倒置,但不排列,如a中元素为1,3,2,4,倒置后为4,2,3,1
23. copy(a.begin(),a.end(),b.begin()+1); //把a中的从a.begin()(包括它)到a.end()(不包括它)的元素复制到b中,从b.begin()+1的位置(包括它)开 始复制,覆盖掉原有元素
24. find(a.begin(),a.end(),10); //在a中的从a.begin()(包括它)到a.end()(不包括它)的元素中查找10,若存在返回其在向量中的位置
1.二分查找:
只能对有序数列使用,内部通过二分查找实现
lower_bound(a,a+n,m):返回数组a中,0~n里第一个大于等于m的指针
int pos=lower_bound(a,a+n,m)-a:返回数组a中,0~n里第一个大于等于m的位置
upper_bound(a,a+n,m):返回数组a中,0~n里第一个大于m的指针
int pos=upper_bound(a,a+n,m)-a:返回数组a中,0~n里第一个大于m的位置
lower_bound(seq2, seq2+6, 7, greater<int>()):加greater<int>()后,lower变小于等于,upper变小于
2.排列:
头文件:#include< algorithm>
next_permutation(a,a+3) //求数组a中前3个元素的下一个排列
prev_permutation(a,a+3) //求数组a中前3个元素的前一个排列
a可为普通数组,string,vector……
求1-10的第10个排列
a[10]={1,2,3,4,5,6,7,8,9,10};
for(int i=1;i< k;i++)
next_permutation(a,a+10);
for(int i=0;i<10;i++)
cout<< a[i]< < ” “;
二.栈
建立:stack< T >S;
入栈:S.push(a);
取栈顶元素:S.top();
删除栈顶元素:S.pop();
1.单调栈
https://blog.csdn.net/wubaizhe/article/details/70136174
利用单调栈,可以找到从左/右遍历第一个比它小/大的元素的位置.由此也可知道该区间上数的个数O(n)
可统计出a[i]~a[n],以i为起点的单调序列长度,从后向前建立单调栈,常做预处理用
一个元素向左遍历的第一个比它小的数的位置就是将它插入单调栈时栈顶元素的值,若栈为空,则说明不存在这么一个数。然后将此元素的下标存入栈,就能类似迭代般地求解后面的元素
#include <iostream>
#include <stack>
using namespace std;
stack<int>s;
int n;
int ans[N]; //ans[i],表示第i个元素,向左遍历,第一个比它小的元素的位置
void getans(int a[])
{
for(int i=1;i<=n;i++)
{
while(s.size() && a[s.top()]>=a[i]) s.pop();
if(s.empty()) ans[i]=0;
else ans[i]=s.top();
s.push(i);
}
}
三.队列
建立:queue< T>Q;
入队:Q.push(a);
出队:Q.front();
删除:Q.pop();
1.优先队列
头文件:#include< queue>
默认从大到小排列
声明:
普通优先队列:priority_queue< int>Q;
从小到大优先队列:priority_queue< int,vector< int>,greater< int> >Q;
对结构体应用优先队列:
struct Node
{
int x,y,val;
friend bool operator < (node n1,node n2)
{
if(n1.val==n2.val)
return n1.x>n2.x;
return n1.val<n2.val; //和正常不同,"<"为从大到小,">"为从小到大
}
};
priority_queue<node>Q
2.单调队列
整理归纳单调队列的定义:
1、维护区间最值;
2、去除冗杂状态;
3、保持队列单调(最大值是单调递减序列,最小值是单调递增序列);
4、最优选择在队首。
在维护好一个 区间正确、严格递减 的单调递减队列后,队列头就是当前区间的最大值了
整理归纳单调队列的使用方法:
1、维护队首(对于上题就是如果你已经是当前的m个之前那你就可以被删了) ;
2、在队尾插入(每插入一个就要从队尾开始往前去除冗杂状态) ;
3、取出需要的最优解(队列头的值即是);
4、借助最优解,得到目前所求的最优解(通常此处插入DP方程)。
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int a[200000];
struct node
{
int x,p;
node(){}
node(int xx,int pp){x=xx;p=pp;}
}list[200000];
int main()
{
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
int head=1,tail=1;
list[1]=node(a[1],1);
for(int i=2;i<=n;i++)
{
while(head<=tail&&list[tail].x<=a[i]) tail--;//删尾
list[++tail]=node(a[i],i);//得到最优解并插入
while(i-list[head].p>=m) head++;//去头
if(i>=m) printf("%d\n",list[head]);
}
return 0;
}
四.向量
可作为数组的替代,但效率不如数组
建立:vector< T >V;
加入:V.push_back(a);
删除:V.earse()
五.链表
stl中默认实现双向链表
建立:list< T>L;
L.assign(a) 给list赋值
L.back() 返回最后一个元素
L.erase() 删除一个元素
L.front() 返回第一个元素
L.insert() 插入一个元素到list中
L.pop_back() 删除最后一个元素
L.pop_front() 删除第一个元素
L.push_back() 在list的末尾添加一个元素
L.push_front() 在list的头部添加一个元素
L.remove() 从list删除元素
L.remove_if() 按指定条件删除元素
1.链式前向星
即用数组模拟链表
#include <iostream>
#include <string.h>
using namespace std;
int const MAX_M=100000; //一定要开很大,否则会TLE
int const MAX_N=100000;
struct Edge
{
int to,cost,next;
}e[MAX_M];
int eid,p[MAX_N];
void init()
{
eid=0;
memset(p,-1,sizeof(p));
}
void insert(int u,int v,int c)
{
e[eid].to=v;
e[eid].cost=c;
e[eid].next=p[u];
p[u]=eid++;
}
void addedge(int u,int v,int c)
{
insert(u,v,c);
insert(v,u,0);
}
int main()
{
init() //初始化,每次必须要有!
addedge(a,b,c); //读入边
for(int i=p[u];i!=-1;i=e[i].next) //遍历u连向的所有边
{
cout<<u<<"->"<<e[i].to<<e[i].next<<e[i].cost;
}
}
2.舞蹈链(dancing links)
适用范围:解决精确覆盖问题,例如数独,n皇后问题;以及重复覆盖问题。
精确覆盖:在一个全集X中若干子集的集合为S,精确覆盖(Exactcover)是指,S的子集S*,满足X中的每一个元素在S*中恰好出现一次
矩阵表示法:
包含关系可以用一个关系矩阵表示。.矩阵每行表示S的一个子集,每列表示X中的一个元素。矩阵行列交点元素为1表示对应的元素在对应的集合中,不在则为0。
通过这种矩阵表示法,求一个精确覆盖转化为求矩阵的若干个行的集合,使每列有且仅有一个1。同时,该问题也是精确覆盖的典型例题之一。
图论表示法:
可将精确覆盖问题转化为一个二分图,左侧为集合,右侧为元素,左侧集合若与右侧元素有包含关系则连边,通过将左侧节点与其所有边保留与否求解一个右侧的每一个节点恰好有一条边的匹配。
重复覆盖问题:
即选取一个01矩阵中的几行,使这几行组成的新矩阵的每一列至少有一个1。 该问题在精确覆盖问题上减少了一个约束条件。
struct DLX//dancing links
{
int U[maxnode],D[maxnode],L[maxnode],R[maxnode],col[maxnode],row[maxnode];//元素上下左右对应列对应行的指针
int S[maxn],H[maxn],V[maxn];//S为每列元素个数,H指向每行末尾的元素,V是dep()函数的标记数组。
int n,m,size,all;//all为解的行数的最大值
void init(int _n,int _m,int _all)
{
n=_n;
m=_m;
all=_all;
for(int i=0;i<=m;i++)
{
L[i]=i-1;
R[i]=i+1;
U[i]=i;
D[i]=i;
row[i]=0;
col[i]=i;
}
clr(S);
clrlow(H);
L[0]=m;
R[m]=0;
size=m;
return ;
}
//初始化
void push(int r,int c)
{
D[++size]=D[c];
col[size]=U[size]=c;
U[D[c]]=size;
D[c]=size;
row[size]=r;
S[c]++;
if(H[r]<0)
{
H[r]=L[size]=R[size]=size;
}
else
{
L[size]=H[r];
R[size]=R[H[r]];
L[R[H[r]]]=size;
R[H[r]]=size;
}
return ;
}
//加入元素
void del(int c)
{
S[col[c]]--;
for(int i=D[c];i!=c;i=D[i])
{
R[L[i]]=R[i];
L[R[i]]=L[i];
S[col[i]]--;
}
return ;
}
//删除一列
void reback(int c)
{
for(int i=U[c];i!=c;i=U[i])
{
S[col[R[L[i]]=L[R[i]]=i]]++;
}
S[col[c]]++;
return ;
}
//恢复一列
int dep( )
{
clr(V);
int deep=0;
for(int i=R[0];i!=0;i=R[i])
if(!V[i])
{
deep++;
for(int j=D[i];j!=i;j=D[j])
for(int k=R[j];k!=j;k=R[k])
V[col[k]]=1;
}
return deep;
}
//之后到达的最大深度
//d为当前深度
bool dancing(int d)
{
if(d+dep()>all) return false;
int c=R[0];
if(c==0)
{
return d<=all;
}
for(int i=R[0];i!=0;i=R[i])
if(S[i]<S[c])
c=i;
for(int i=D[c];i!=c;i=D[i])
{
del(i);
for(int j=R[i];j!=i;j=R[j])
del(j);
if(dancing(d+1)) return true;
for(int j=L[i];j!=i;j=L[j])
reback(j);
reback(i);
}
return false;
}
//dancing
}dlx;
六.堆
STL中的建立的队默认是最大堆,要想用最小堆的话,必须要在push_heap,pop_heap,make_heap等每一个函数后面加第三个参数greater< int>(),括号不能省略
make_heap(_First, _Last, _Comp); //默认是建立最大堆的。对int类型,可以在第三个参数传入greater()得到最小堆。
push_heap (_First, _Last); //在堆中添加数据,要先在容器中加入数据,再调用push_heap ()
pop_heap(_First, _Last); //在堆中删除数据,要先调用pop_heap()再在容器中删除数据
sort_heap(_First, _Last); //堆排序,排序之后就不再是一个合法的heap了
1.映射二叉堆:
利用set实现,可以在log(n)的时间进行增,删,改,查操作,利用优先级进行排序,再映射到相应内容
#define PII pair<int, int>
set<PII, greater<PII>> gheap; // 定义了一个大根堆
set<PII, less<PII>> lheap; // 定义了一个小根堆
//pair的first储存关键字(优先级),second储存索引或内容
int keys[MAX_INDEX]; // 存储每个索引对应的内容,如果索引的范围很大,可以用 map<int, int> 来储存
//堆的插入
gheap.insert(make_pair(value, id));
//取堆顶元素
set<PII, greater<PII>>::iterator iter = gheap.begin();
cout << iter->first << " " << iter->second << endl; // 第一个数是堆顶元素的关键字,第二个数是堆顶元素的索引
//取堆尾元素
set<PII, greater<PII>>::reverse_iterator riter = gheap.rbegin();
cout<< riter->first<<" "<<riter->second<<endl;
//删除指定元素
gheap.erase(make_pair(keys[idx], idx));
//删除堆顶元素
gheap.erase(*(gheap.begin()));
//删除堆尾元素
riter=gheap.rbegin();
int a=riter->first;
int b=riter->second;
gheap.erase(make_pair(a,b));
七.集合
内部通过红黑树实现,各种操作均为O(logn),集合中没有重复元素,且默认按从小到大排序
头文件:#include<set>
声明:set<T>s;
迭代器:set<T>::iterator;
插入:A.insert("Tom");
删除:A.erase("Tom"); 只支持正向迭代器
查找:A.count("Tom");
遍历:set<string>::iterator it;
for(it=A.begin();it!=A.end();it++)
cout<<(*it);
正向:
A.begin(); 返回集合中第一个元素的迭代器
A.end(); 返回集合中最后一个元素下一位置的迭代器
反向:
set<T>:: reverse_iterator;
A.rbegin(); 返回集合中最后一个元素的反向迭代器
A.rend(); 返回集合中第一个元素前一位置的反向迭代器
清空:A.clear();
取并:set_union(A.begin(),A.end(),B.begin(),B.end(),inserter( C , C.begin() ) ); 将A集合与B集合取并后存入C集合,要去A,B必须有序
取交:set_intersection(A.begin(),A.end(),B.begin(),B.end(),inserter( C , C.begin() )); 取交集,同上
八.映射
基本与set相同,常用于将字符串映射成数字等操作
头文件:#include<map>
声明:map<T1,T2>m;
插入:A.insert(pari<string,int>("Tom",1));
A["Tom"]=1;
访问:cout<<A["Tom"];
查找:A.count("Tom")
若存在返回1,否则返回0
遍历:map<string,int>::iterator it;
for(it=A.begin();it!=A.end();it++)
cout<<it->first<<it->second;
清空:A.clear();
删除:A.erase(it);
九.ST表
常用于RMQ问题,即区间最值查询,不支持修改,预处理O(nlogn),每次查询仅为O(1)
void REQ_init(vector<int> A) {
int n = A.size();
for (int i = 0; i < n; ++i) f[i][0] = A[i];
for (int j = 1; (1 << j) <= n; ++j) // 枚举长度 2^j
for (int i = 0; i + (1 << j) - 1 < n; ++i) {
f[i][j] = max(f[i][j - 1], f[i + (1 << (j - 1))][j - 1]);
}
}
int RMQ(int L, int R) {
int k = 0;
while ((1 << (k + 1)) <= R - L + 1) ++k;
return max(f[L][k], f[R - (1 << k) + 1][k]);
}
十.并查集
用于维护各个点之间的关系,可快速判断某点是否属于某一类集合
int fa[N],rank[N];
void init(int n) //初始化
{
for(int i=0;i<n;i++)
{
fa[i]=i;
rank[i]=0;
}
}
int find(int x)
{
if(fa[x]==x)
return x;
else
return fa[x]=find(fa[x]); //路径压缩
}
void unite(int x,int y)
{
x=find(x);
y=find(y);
if(x==y) return ;
if(rank[x]<rank[y])
fa[x]=y;
else
{
fa[y]=x;
if(rank[x]==rank[y])
rank[x]++;
}
}
bool same(int x,int y)
{
return find(x)==find(y);
}
十一.树状数组
利用二进制性质,可在O(logn)对区间前缀进行查询和修改操作
#include <iostream>
#include <string.h>
using namespace std;
const int N=100050;
int c[N],ans[N]; //c[n]表示a[1~n]的和,a数组省略
int lowbit(int x) //求2^k
{
return x & -x;
}
int getsum(int n) //区间查询,求a[1~n]的和
{
int res = 0;
while(n>0)
{
res+=c[n];
n=n-lowbit(n);
}
return res;
}
int change(int x) //单点更新,将a[x]的值加1
{
while(x<=N)
{
c[x]++;
x+=lowbit(x);
}
}
int main()
{
int n;
cin>>n;
memset(c,0,sizeof(c));
memset(ans,0,sizeof(ans));
for(int i=0;i<n;i++)
{
int x,y;
cin>>x>>y;
x++;
ans[getsum(x)]++;
change(x);
}
for(int i=0;i<n;i++)
cout<<ans[i]<<endl;
return 0;
}
十二.线段树
比树状数组更为灵活,功能更加强大,对于区间操作,支持单点修改,区间修改,区间查询等
单点操作,区间查询:
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<queue>
#include<cstring>
#include<cmath>
using namespace std;
#define INF 10000000
#define lson l,mid,rt<<1 //左儿子
#define rson mid+1,r,rt<<1|1 //右儿子
const int maxn = 222222;
struct Node{
int Max,Min; //区间的最大值和最小值
int sum; //区间的和
}stree[maxn<<2];
void up(int rt){ //更新该区间的最值与和
stree[rt].Max=max(stree[rt<<1].Max,stree[rt<<1|1].Max);
stree[rt].Min=min(stree[rt<<1].Min,stree[rt<<1|1].Min);
stree[rt].sum=stree[rt<<1].sum+stree[rt<<1|1].sum;
}
void build(int l,int r,int rt){ //在结点i上建立区间为[l,r]
if(l==r){ //叶子结点
int num;
scanf("%d",&num);
stree[rt].Max=stree[rt].Min=stree[rt].sum=num;
return ;
}
int mid=(l+r)>>1;
build(lson); //建立左儿子
build(rson); //建立右儿子
up(rt); //更新
}
int querymax(int a,int b,int l,int r,int rt){ //求区间[a,b]的最大值
if(a<=l&&r<=b){ //如果全包含,直接取区间最大值
return stree[rt].Max;
}
int mid = (r+l)>>1;
int ret = -INF;
if(a<=mid) ret=max(ret,querymax(a,b,lson));//如果左端点在中点的左边,找出左区间的最大值
if(mid<b) ret=max(ret,querymax(a,b,rson));//如果右端点在中点的右边,找出右区间(以及左区间)的最大值
return ret;
}
int querymin(int a,int b,int l,int r,int rt){ //求区间[a,b]的最小值
if(a<=l&&r<=b){ //如果全包含,直接取区间最小值
return stree[rt].Min;
}
int mid = (r+l)>>1;
int ret = INF;
if(a<=mid) ret=min(ret,querymin(a,b,lson));//如果左端点在中点的左边,找出左区间的最小值
if(mid<b) ret=min(ret,querymin(a,b,rson)); //如果右端点在中点的右边,找出右区间(以及左区间)的最小值
return ret;
}
int querysum(int a,int b,int l,int r,int rt){ //求区间[a,b]的和(a,b的值相同时为求单点的值)
if(a<=l&&r<=b){ //如果全包含,直接取区间的和
return stree[rt].sum;
}
int mid = (r+l)>>1;
int ret=0;
if(a<=mid) ret+=querysum(a,b,lson);
if(mid<b) ret+=querysum(a,b,rson);
return ret;
}
void uppoint(int a,int b,int l,int r,int rt){ //单点替换,把第a个数换成b
if(l==r){
stree[rt].Max=stree[rt].Min=stree[rt].sum=b;
return ;
}
int mid =(r+l)>>1;
if(a<=mid)uppoint(a,b,lson);
else uppoint(a,b,rson);
up(rt);
}
void upadd(int a,int b,int l,int r,int rt){ //单点增减,把第a个数增减b
if(l==r){
stree[rt].sum=stree[rt].sum+b;
stree[rt].Max=stree[rt].Max+b;
stree[rt].Min=stree[rt].Min+b;
return ;
}
int mid=(l+r)>>1;
if(a<=mid) upadd(a,b,lson);
else upadd(a,b,rson);
up(rt);
}
int main()
{
//freopen("F:\\11.txt","r",stdin);
int n,q;
while(~scanf("%d%d",&n,&q)){
build(1,n,1);//build(l,r,rt);
while(q--){
char op[10];
int a,b;
scanf("%s%d%d",op,&a,&b);
if(op[0]=='X'){//求区间[a,b]的最大值
printf("%d\n",querymax(a,b,1,n,1));//querymax(int a,int b,int l,int r,int rt);
}
else if(op[0]=='N'){//求区间[a,b]的最小值
printf("%d\n",querymin(a,b,1,n,1));//querymin(int a,int b,int l,int r,int rt);
}
else if(op[0]=='U'){//单点替换,把第a个数换成b
uppoint(a,b,1,n,1);//uppoint(int a,int b,int l,int r,int rt);
}
else if(op[0]=='S'){//求区间[a,b]的和(a,b的值相同时为求单点的值)
printf("%d\n",querysum(a,b,1,n,1));//querysum(int a,int b,int l,int r,int rt);
}
else if(op[0]=='A'){//单点增加,把第a个数增加b
upadd(a,b,1,n,1);
}
else if(op[0]=='E'){//单点减少,把第a个数减少b
upadd(a,-b,1,n,1);
}
}
}
return 0;
}
区间替换,区间查询:
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#define max(a,b) (a>b)?a:b
#define min(a,b) (a>b)?b:a
#define lson l , m , rt << 1
#define rson m + 1 , r , rt << 1 | 1
const int maxn = 100100;
const int INF=0x7fffffff;
using namespace std;
int lazy[maxn<<2];
int MAX[maxn<<2];
int MIN[maxn<<2];
int SUM[maxn<<2];
void PushUp(int rt) { //由左孩子、右孩子向上更新父节点
SUM[rt] = SUM[rt<<1] + SUM[rt<<1|1];
MAX[rt] = max(MAX[rt<<1],MAX[rt<<1|1]);
MIN[rt] = min(MIN[rt<<1],MIN[rt<<1|1]);
}
void PushDown(int rt,int m) { //向下更新
if (lazy[rt]) { //懒惰标记
lazy[rt<<1] = lazy[rt<<1|1] = lazy[rt];
SUM[rt<<1] = (m - (m >> 1)) * lazy[rt];
SUM[rt<<1|1] = ((m >> 1)) * lazy[rt];
MAX[rt<<1]=MAX[rt<<1|1]=lazy[rt];
MIN[rt<<1]=MIN[rt<<1|1]=lazy[rt];
lazy[rt] = 0;
}
}
//所有的l,r,rt 带入1,n,1
void build(int l,int r,int rt) { //初始化建树
lazy[rt] = 0;
if (l== r) {
SUM[rt]=MAX[rt]=MIN[rt]=0; //初始化为0的建树
/*scanf("%d",&SUM[rt]); //边读入边建树的方法
MAX[rt]=MIN[rt]=SUM[rt];
*/
return ;
}
int m = (l + r) >> 1;
build(lson);
build(rson);
PushUp(rt);
}
void update(int L,int R,int v,int l,int r,int rt) { //将L~R区间的值置为v
//if(L>l||R>r) return;
if (L <= l && r <= R) {
lazy[rt] = v;
SUM[rt] = v * (r - l + 1);
MIN[rt] = v;
MAX[rt] = v;
//printf("%d %d %d %d %d\n", rt, sum[rt], c, l, r);
return ;
}
PushDown(rt , r - l + 1);
int m = (l + r) >> 1;
if (L <= m) update(L , R , v , lson);
if (R > m) update(L , R , v , rson);
PushUp(rt);
}
int querySUM(int L,int R,int l,int r,int rt) { //求区间L~R的和
if (L <= l && r <= R) {
//printf("%d\n", sum[rt]);
return SUM[rt];
}
PushDown(rt , r - l + 1);
int m = (l + r) >> 1;
int ret = 0;
if (L <= m) ret += querySUM(L , R , lson);
if (m < R) ret += querySUM(L , R , rson);
return ret;
}
int queryMIN(int L,int R,int l,int r,int rt) { //求区间L~R的最小值
if (L <= l && r <= R) {
//printf("%d\n", sum[rt]);
return MIN[rt];
}
PushDown(rt , r - l + 1);
int m = (l + r) >> 1;
int ret = INF;
if (L <= m) ret = min(ret, queryMIN(L , R , lson));
if (m < R) ret = min(ret,queryMIN(L , R , rson));
return ret;
}
int queryMAX(int L,int R,int l,int r,int rt) { //求区间L~R的最大值
if (L <= l && r <= R) {
//printf("%d\n", sum[rt]);
return MAX[rt];
}
PushDown(rt , r - l + 1);
int m = (l + r) >> 1;
int ret = -INF;
if (L <= m) ret = max(ret, queryMAX(L , R , lson));
if (m < R) ret = max(ret,queryMAX(L , R , rson));
return ret;
}
int main() {
int n , m;
char str[5];
while(scanf("%d%d",&n,&m)) {
build(1 , n , 1);
while (m--) {
scanf("%s",str);
int a , b , c;
if(str[0]=='T') {
scanf("%d%d%d",&a,&b,&c);
update(a , b , c , 1 , n , 1);
} else if(str[0]=='Q') {
scanf("%d%d",&a,&b);
cout<<querySUM(a,b,1,n,1)<<endl;
} else if(str[0]=='A') {
scanf("%d%d",&a,&b);
cout<<queryMAX(a,b,1,n,1)<<endl;
} else if(str[0]=='I') {
scanf("%d%d",&a,&b);
cout<<queryMIN(a,b,1,n,1)<<endl;
}
}
}
return 0;
}
区间增加,区间查询:
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#define max(a,b) (a>b)?a:b
#define min(a,b) (a>b)?b:a
#define lson l , m , rt << 1
#define rson m + 1 , r , rt << 1 | 1
const int maxn = 100100;
const int INF=0x7fffffff;
using namespace std;
int lazy[maxn<<2];
int SUM[maxn<<2],MAX[maxn<<2],MIN[maxn<<2];
void putup(int rt) {
SUM[rt] = SUM[rt<<1] + SUM[rt<<1|1];
MAX[rt] =max(MAX[rt<<1],MAX[rt<<1|1]) ;
MIN[rt] =min(MIN[rt<<1],MIN[rt<<1|1]);
}
void putdown(int rt,int m) {
if (lazy[rt]) {
lazy[rt<<1] += lazy[rt];
lazy[rt<<1|1] += lazy[rt];
SUM[rt<<1] += lazy[rt] * (m - (m >> 1));
SUM[rt<<1|1] += lazy[rt] * (m >> 1);
MAX[rt<<1]+=lazy[rt];
MAX[rt<<1|1] +=lazy[rt];
MIN[rt<<1]+=lazy[rt];
MIN[rt<<1|1]+=lazy[rt];
lazy[rt] = 0;
}
}
//以下的 l,r,rt 都带入 1,n,1
void build(int l,int r,int rt) { //初始化建树
lazy[rt] = 0;
if (l == r) {
//初始化树为0的写法
SUM[rt]=MAX[rt]=MIN[rt]=0;
/* //边读入边建树的写法
scanf("%d",&SUM[rt]);
MAX[rt]=MIN[rt]=SUM[rt];
*/
return ;
}
int m = (l + r) >> 1;
build(lson);
build(rson);
putup(rt);
}
void update(int L,int R,int v,int l,int r,int rt) { //将区间L~R的值增加v
if (L <= l && r <= R) {
lazy[rt] += v;
SUM[rt] += v * (r - l + 1);
MAX[rt]+=v;
MIN[rt]+=v;
return ;
}
putdown(rt , r - l + 1);
int m = (l + r) >> 1;
if (L <= m) update(L , R , v , lson);
if (m < R) update(L , R , v , rson);
putup(rt);
}
int querySUM(int L,int R,int l,int r,int rt) { //求区间L~R的和
if (L <= l && r <= R) {
return SUM[rt];
}
putdown(rt , r - l + 1);
int m = (l + r) >> 1;
int ret = 0;
if (L <= m) ret += querySUM(L , R , lson);
if (m < R) ret += querySUM(L , R , rson);
return ret;
}
int queryMAX(int L,int R,int l,int r,int rt) { //求区间L~R的最大值
if (L <= l && r <= R) {
return MAX[rt];
}
putdown(rt , r - l + 1);
int m = (l + r) >> 1;
int ret = -INF;
if (L <= m) ret =max(ret,queryMAX(L , R , lson)) ;
if (m < R) ret =max(ret,queryMAX(L , R , rson)) ;
return ret;
}
int queryMIN(int L,int R,int l,int r,int rt) { //求区间L~R的最小值
if (L <= l && r <= R) {
return MIN[rt];
}
putdown(rt , r - l + 1);
int m = (l + r) >> 1;
int ret = INF;
if (L <= m) ret = min(ret,queryMIN(L , R , lson));
if (m < R) ret = min(ret,queryMIN(L , R , rson));
return ret;
}
int main() {
int n , m;
int a , b , c;
char str[5];
scanf("%d%d",&n,&m);
build(1 , n , 1);
while (m--) {
scanf("%s",str);
if (str[0] == 'S') {
scanf("%d%d",&a,&b);
printf("%d\n",querySUM(a , b , 1 , n , 1));
} else if(str[0]=='C') {
scanf("%d%d%d",&a,&b,&c);
update(a , b , c , 1 , n , 1);
} else if(str[0]=='A') {
scanf("%d%d",&a,&b);
printf("%d\n",queryMAX(a , b , 1 , n , 1));
} else if(str[0]=='I') {
scanf("%d%d",&a,&b);
printf("%d\n",queryMIN(a , b , 1 , n , 1));
}
}
return 0;
}
1.zkw线段树:
代码简单,效率高,区间修改较难,建议用普通线段树
单点操作,区间查询:
#include <iostream>
#include <stdio.h>
#include <string>
using namespace std;
const int N=50005;
int n,M,q;
int d[N*4];
void build(int n)
{
for(M=1;M<n;M<<=1);
for(int i=M+1;i<=M+n;i++)
scanf("%d",&d[i]);
for(int i=M-1;i;i--)
d[i]=d[i<<1]+d[i<<1|1];
//d[i]=max(d[i<<1],d[i<<1|1]); 求最值
}
void update_add(int x,int y)
{
d[x+=M]+=y;
x>>=1;
for(;x;x>>=1)
d[x]=d[x<<1]+d[x<<1|1];
}
void update_sub(int x,int y)
{
d[x+=M]-=y;
x>>=1;
for(;x;x>>=1)
d[x]=d[x<<1]+d[x<<1|1];
}
void update_change(int x,int y)
{
d[x+=M]=y;
for(x>>=1;x;x>>=1)
d[x]=max(d[x<<1],d[x<<1|1]);
}
int query_max(int s,int t)
{
int ans=-1;
for(s+=M-1,t+=M+1;s^t^1;s>>=1,t>>=1)
{
if(~s&1)
ans=max(ans,d[s^1]);
if(t&1)
ans=max(ans,d[t^1]);
}
return ans;
}
int query_sum(int s,int t)
{
int ans=0;
for(s+=M-1,t+=M+1;s^t^1;s>>=1,t>>=1)
{
if(~s&1)
ans+=d[s^1];
if(t&1)
ans+=d[t^1];
}
return ans;
}
int main()
{
int a,b;
string str;
cin>>n;
build(n);
while(cin>>str)
{
if(str=="End")
break;
if(str=="Query")
{
cin>>a>>b;
cout<<query(a,b)<<endl;
}
if(str=="Add")
{
cin>>a>>b;
update_add(a,b);
}
if(str=="Sub")
{
cin>>a>>b;
update_sub(a,b);
}
}
return 0;
}
3.主席树
每个结点都是一棵线段树,维护[1~i]的前缀信息,各操作均为O(logn)
特点:查询区间第K大;将当前版本返回之前某版本;在之前某版本上区间查询
https://blog.csdn.net/creatorx/article/details/75446472
https://blog.csdn.net/CillyB/article/details/75912440
用主席树求区间第K大问题,包含离散化:
#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn = 1e5 + 10;
int n, m;
int cnt;
struct node{
int L, R;//分别指向左右子树
int sum;//该节点所管辖区间范围内数的个数
node(){
sum = 0;
}
}Tree[maxn * 20];
struct value{
int x;//值的大小
int id;//离散之前在原数组中的位置
}Value[maxn];
bool cmp(value v1, value v2)
{
return v1.x < v2.x;
}
int root[maxn];//多颗线段树的根节点
int rank[maxn];//原数组离散之后的数组
void init()
{
cnt = 1;
root[0] = 0;
Tree[0].L = Tree[0].R = Tree[0].sum = 0;
}
void update(int num, int &rt, int l, int r)
{
Tree[cnt++] = Tree[rt];
rt = cnt - 1;
Tree[rt].sum++;
if(l == r) return;
int mid = (l + r)>>1;
if(num <= mid) update(num, Tree[rt].L, l, mid);
else update(num, Tree[rt].R, mid + 1, r);
}
int query(int i, int j, int k, int l, int r) //查询第K小的数
{
int d = Tree[Tree[j].L].sum - Tree[Tree[i].L].sum;
if(l == r) return l;
int mid = (l + r)>>1;
if(k <= d) return query(Tree[i].L, Tree[j].L, k, l, mid);
else return query(Tree[i].R, Tree[j].R, k - d, mid + 1, r);
}
int query_lessKNum(int i,int j,int k,int l,int r) //查询小于等于k的数有几个
{
if(l==r) return Tree[j].sum - Tree[i].sum;
int mid=(l+r)>>1;
int res=0;
if(k<=mid) res+=query_lessKNum(Tree[i].L,Tree[j].L,k,l,mid);
else
{
res+=Tree[Tree[j].L].sum-Tree[Tree[i].L].sum;
res+=query_lessKNum(Tree[i].R,Tree[j].R,k,mid+1,r);
}
return res;
}
int main()
{
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++)
{
scanf("%d", &Value[i].x);
Value[i].id = i;
}
//进行离散化
sort(Value + 1, Value + n + 1, cmp);
for(int i = 1; i <= n; i++)
{
rank[Value[i].id] = i;
}
init();
for(int i = 1; i <= n; i++)
{
root[i] = root[i - 1];
update(rank[i], root[i], 1, n);
}
int left, right, k;
for(int i = 1; i <= m; i++)
{
scanf("%d%d%d", &left, &right, &k);
printf("%d\n", Value[query(root[left - 1], root[right], k, 1, n)].x);
}
return 0;
}
十三.平衡树:
用于动态维护数据,可求特定值的排名,特定排名的值,某值的前驱,某值的后继
1.Splay树
常用方法
splay主要可以用来解决区间的维护问题
假设我们需要维护一个数列,支持
1.在数列第i位后插入一个长为l的数列
2.在数列第i为后删除一个长为l的数列
3.将数列的l r区间翻转(1 2 3 2 3 翻转后为 3 2 3 2 1)
4.将数列的l r区间同时加上一个值
5.将数列的l r区间同时改为一个值
6.求数列的l r区间的和(最大值)
其实线段树上的大部分操作这里都支持,比如区间最大子区间和
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
#define MAXN 1000000
int ch[MAXN][2],f[MAXN],size[MAXN],cnt[MAXN],key[MAXN];
int sz,root;
inline void clear(int x){ //初始化
ch[x][0]=ch[x][1]=f[x]=size[x]=cnt[x]=key[x]=0;
}
inline bool get(int x){ // 判断当前点是它父结点的左儿子还是右儿子
return ch[f[x]][1]==x;
}
inline void update(int x){ // 更新当前点的size值(用于发生修改之后)
if (x){
size[x]=cnt[x];
if (ch[x][0]) size[x]+=size[ch[x][0]];
if (ch[x][1]) size[x]+=size[ch[x][1]];
}
}
inline void rotate(int x){ //旋转,保持平衡
int old=f[x],oldf=f[old],whichx=get(x);
ch[old][whichx]=ch[x][whichx^1]; f[ch[old][whichx]]=old;
ch[x][whichx^1]=old; f[old]=x;
f[x]=oldf;
if (oldf)
ch[oldf][ch[oldf][1]==old]=x;
update(old); update(x);
}
inline void splay(int x){ //伸展,保持平衡
for (int fa;fa=f[x];rotate(x))
if (f[fa])
rotate((get(x)==get(fa))?fa:x);
root=x;
}
inline void insert(int x){ //插入
if (root==0){sz++; ch[sz][0]=ch[sz][1]=f[sz]=0; root=sz; size[sz]=cnt[sz]=1; key[sz]=x; return;}
int now=root,fa=0;
while(1){
if (x==key[now]){
cnt[now]++; update(now); update(fa); splay(now); break;
}
fa=now;
now=ch[now][key[now]<x];
if (now==0){
sz++;
ch[sz][0]=ch[sz][1]=0;
f[sz]=fa;
size[sz]=cnt[sz]=1;
ch[fa][key[fa]<x]=sz;
key[sz]=x;
update(fa);
splay(sz);
break;
}
}
}
inline int find(int x){ //查询x点的排名
int now=root,ans=0;
while(1){
if (x<key[now])
now=ch[now][0];
else{
ans+=(ch[now][0]?size[ch[now][0]]:0);
if (x==key[now]){
splay(now); return ans+1;
}
ans+=cnt[now];
now=ch[now][1];
}
}
}
inline int findx(int x){ //查询排名为x的结点
int now=root;
while(1){
if (ch[now][0]&&x<=size[ch[now][0]])
now=ch[now][0];
else{
int temp=(ch[now][0]?size[ch[now][0]]:0)+cnt[now];
if (x<=temp) return key[now];
x-=temp; now=ch[now][1];
}
}
}
inline int pre(){ //求前驱
int now=ch[root][0];
while (ch[now][1]) now=ch[now][1];
return now;
}
inline int next(){ //求后继
int now=ch[root][1];
while (ch[now][0]) now=ch[now][0];
return now;
}
inline void del(int x){ //删除
int whatever=find(x);
if (cnt[root]>1){cnt[root]--; update(root); return;}
if (!ch[root][0]&&!ch[root][1]) {clear(root); root=0; return;}
if (!ch[root][0]){
int oldroot=root; root=ch[root][1]; f[root]=0; clear(oldroot); return;
}
else if (!ch[root][1]){
int oldroot=root; root=ch[root][0]; f[root]=0; clear(oldroot); return;
}
int leftbig=pre(),oldroot=root;
splay(leftbig);
ch[root][1]=ch[oldroot][1];
f[ch[oldroot][1]]=root;
clear(oldroot);
update(root);
}
int main(){
int n,opt,x;
scanf("%d",&n);
for (int i=1;i<=n;++i){
scanf("%d%d",&opt,&x);
switch(opt){
case 1: insert(x); break;
case 2: del(x); break;
case 3: printf("%d\n",find(x)); break;
case 4: printf("%d\n",findx(x)); break;
case 5: insert(x); printf("%d\n",key[pre()]); del(x); break;
case 6: insert(x); printf("%d\n",key[next()]); del(x); break;
}
}
}
2.Treap树堆
较常用的平衡树,综合性能优秀
#include<iostream>
#include<cstdio>
#include<cstdlib>
using namespace std;
struct TREAP{
int l,r,val,sz,recy,rd;
//sz表示树的节点数,recy记录自己被重复了几次
//rd表示该节点的优先级
}t[1000000];
int m,size,root,ans;
void update(int k){
t[k].sz=t[t[k].l].sz+t[t[k].r].sz+t[k].recy;
//更新维护
}
void left_rotate(int &k){
int y=t[k].r;t[k].r=t[y].l;t[y].l=k;
t[y].sz=t[k].sz;update(k);k=y;
//左旋,至于这里的k=y,由于下面的递归调用,
//它会一直迭代,所以无需担心会有什么错误
}
void right_rotate(int &k){
int y=t[k].l;t[k].l=t[y].r;t[y].r=k;
t[y].sz=t[k].sz;update(k);k=y;
//右旋
}
//以下函数的调用中(int k)表示在根为k的子树中
void insert(int &k,int x){//插入操作
if (k==0){//无节点时特判,
//或是递归的边界,即插入叶节点
++size;k=size;t[k].sz=t[k].recy=1;
t[k].val=x;t[k].rd=rand();return ;
//rand()生成随机的优先级,保证了期望复杂度
}
++t[k].sz;//每次向下找同时增加该节点1个节点数
if (t[k].val==x) ++t[k].recy;
//如果是相同数字,只需++recy即可
else if (x>t[k].val){
insert(t[k].r,x);
if (t[t[k].r].rd<t[k].rd) left_rotate(k);
//插入后如果违反堆性质,就进行上浮
}else{
insert(t[k].l,x);
if (t[t[k].l].rd<t[k].rd) right_rotate(k);
}
}
void del(int &k,int x){
if (k==0) return ;//无节点就跳出
if (t[k].val==x){
if (t[k].recy>1){
--t[k].recy;--t[k].sz;return ;
//如果重复了,只需--recy即可
}
if (t[k].l*t[k].r==0) k=t[k].l+t[k].r;
//如果左右儿子有为空的情况
//或将其变为其儿子节点,或将其删除
else if (t[t[k].l].rd<t[t[k].r].rd)
right_rotate(k),del(k,x);
//如果其左右儿子都有,选择优先级较大的,
//保持以后的堆性质,同时将k节点下沉
else left_rotate(k),del(k,x);
}
else if (x>t[k].val)
--t[k].sz,del(t[k].r,x);
//如果关键值不同,继续向下找
else --t[k].sz,del(t[k].l,x);
}
int atrank(int k,int x){//寻找值为x的数的排名
if (k==0) return 0;
if (t[k].val==x) return t[t[k].l].sz+1;
//如果找的关键字,根据BST的性质,
//则其排名为左子树的大小+1
else if (x>t[k].val)
return t[t[k].l].sz+t[k].recy+atrank(t[k].r,x);
//加上前面所有比它小的数,在右子树中找
else return atrank(t[k].l,x);
//如果在左子树中找的话就不用加了
}
int rerank(int k,int x){//寻找排名为x的数值
if (k==0) return 0;
if (x<=t[t[k].l].sz) return rerank(t[k].l,x);
//如果x小于了左子树的大小,那解一定在左子树中
else if (x>t[t[k].l].sz+t[k].recy)
return rerank(t[k].r,x-t[k].recy-t[t[k].l].sz);
//如果x大于的左子树的大小+k的重复次数,
//那就在右子树中找
else return t[k].val;
//否则就是找到解了(包含了重复数字中)
}
void pred(int k,int x){//找前缀
if (k==0) return ;
if (t[k].val<x){
ans=k;pred(t[k].r,x);
//找到了更优的解,就替换之
//而且在其右子树中不可能再有更优的了
//故向其左子树中找
}else pred(t[k].l,x);
//否则就往右子树中找
}
void succ(int k,int x){//找后缀
if (k==0) return ;
if (t[k].val>x){
ans=k;succ(t[k].l,x);
}else succ(t[k].r,x);
}
int main(){
int f,x;
scanf("%d",&m);
for (int i=1;i<=m;++i){
scanf("%d%d",&f,&x);ans=0;
if (f==1) insert(root,x);
if (f==2) del(root,x);
if (f==3) printf("%d\n",atrank(root,x));
if (f==4) printf("%d\n",rerank(root,x));
if (f==5) {pred(root,x);printf("%d\n",t[ans].val);}
if (f==6) {succ(root,x);printf("%d\n",t[ans].val);}
}
return 0;
}
3.替罪羊树(重量平衡树)
#include<iostream>
#include<cstdio>
#include<cstdlib>
#define inf (1<<30)
#define maxn (2100000)
#define db double
#define il inline
#define RG register
using namespace std;
il int gi(){
RG int x=0,q=1; RG char ch=getchar();
while( ( ch<'0' || ch>'9' ) && ch!='-' ) ch=getchar();
if( ch=='-' ) q=-1,ch=getchar();
while(ch>='0' && ch<='9') x=x*10+ch-48,ch=getchar();
return q*x;
}
const db al=0.75;
struct node{ int son[2],fa,size,num; }t[maxn];
int n,cnt,root;
il bool balance(RG int id){ //平衡限制,这里的 alpha 取0.75
return (db)t[id].size*al>=(db)t[ t[id].son[0] ].size
&& (db) t[id].size*al>=(db)t[t[ id].son[1] ].size;
}
int cur[maxn],sum;
il void recycle(RG int id){ //压扁成一个序列,按大小顺序回收节点
if(t[id].son[0]) recycle(t[id].son[0]);
cur[++sum]=id;
if(t[id].son[1]) recycle(t[id].son[1]);
}
il int build(RG int l,RG int r){ //递归建树
if(l>r) return 0;
RG int mid=(l+r)>>1,id=cur[mid];
t[ t[id].son[0]=build(l,mid-1) ].fa=id;
t[ t[id].son[1]=build(mid+1,r) ].fa=id;
t[id].size=t[ t[id].son[0] ].size+t[ t[id].son[1] ].size+1;
return id;
}
il void rebuild(RG int id){ //重构
sum=0; recycle(id);
RG int fa=t[id].fa,Son=( t[ t[id].fa ].son[1]==id );
RG int cur=build(1,sum);
t[ t[fa].son[Son]=cur ].fa=fa;
if(id==root) root=cur;
}
il void insert(RG int x){
RG int now=root,cur=++cnt;
t[cur].size=1,t[cur].num=x;
while(1){ //插入维护序列,左小右大
t[now].size++;
RG bool Son=(x>=t[now].num);
if( t[now].son[Son] ) now=t[now].son[Son];
else{
t[ t[now].son[Son]=cur ].fa=now;
break;
}
}
RG int flag=0;
for(RG int i=cur;i;i=t[i].fa) if(!balance(i)) flag=i;
if(flag) rebuild(flag); //插入往往会导致不平衡,这时只需要重建不平衡的子树即可
}
il int get_num(RG int x){ //查询 x 在树中的节点编号
RG int now=root;
while(1){
if(t[now].num==x) return now;
else now=t[now].son[ t[now].num<x ];
}
}
il void erase(RG int id){ //删除
if(t[id].son[0] && t[id].son[1]){
RG int cur=t[id].son[0];
while(t[cur].son[1]) cur=t[cur].son[1];
t[id].num=t[cur].num; id=cur;
} //删除操作需要找到左子树的最后一个节点或右子树的第一个节点来顶替,优先找左子树
RG int Son=(t[id].son[0]) ? t[id].son[0]:t[id].son[1];
RG int k=( t[ t[id].fa ].son[1]==id );
t[ t[ t[id].fa ].son[k]=Son ].fa=t[id].fa;
for(RG int i=t[id].fa;i;i=t[i].fa) t[i].size--;
if(id==root) root=Son;
}
il int get_rank(RG int x){ //查 x 的排名
RG int now=root,ans=0;
while(now){
if(t[now].num<x) ans+=t[ t[now].son[0] ].size+1,now=t[now].son[1];
else now=t[now].son[0];
}
return ans;
}
il int get_kth(RG int x){ //查树中的第 k 个数
RG int now=root;
while(1){
if(t[ t[now].son[0] ].size==x-1) return now;
else if(t[ t[now].son[0] ].size>=x) now=t[now].son[0];
else x-=t[ t[now].son[0] ].size+1,now=t[now].son[1];
}
return now;
}
il int get_front(RG int x){ //找前驱,即左子树最后一个点
int now=root,ans=-inf;
while(now){
if(t[now].num<x) ans=max(ans,t[now].num),now=t[now].son[1];
else now=t[now].son[0];
}
return ans;
}
il int get_behind(RG int x){ //找后继,即右子树第一个点
h RG int now=root,ans=inf;
while(now){
if(t[now].num>x) ans=min(ans,t[now].num),now=t[now].son[0];
else now=t[now].son[1];
}
return ans;
}
il void init(){
cnt=2,root=1;
t[1].num=-inf,t[1].size=2,t[1].son[1]=2;
t[2].num=inf,t[2].size=1,t[2].fa=1;
}
il void work(){
n=gi(); RG int type,x;
for(RG int i=1;i<=n;i++){
type=gi(),x=gi();
if(type==1) insert(x);
if(type==2) erase( get_num(x) );
if(type==3) printf("%d\n",get_rank(x));
if(type==4) printf("%d\n",t[ get_kth(x+1) ].num);
if(type==5) printf("%d\n",get_front(x));
if(type==6) printf("%d\n",get_behind(x));
}
}
int main(){ init(); work(); return 0; }
十四.珂朵莉树(ODT)
维护一种数据结构,支持:
区间加,区间覆盖,区间求第k大,区间求x次方和(x≤1e9)(x≤1e9)。
保证数据随机。
#include<bits/stdc++.h>
#define IT set<node>::iterator
using namespace std;
typedef long long LL;
const int MOD7 = 1e9 + 7;
const int MOD9 = 1e9 + 9;
const int imax_n = 1e5 + 7;
struct node
{
int l,r; //范围
mutable LL v; //数值
node(int L, int R=-1, LL V=0):l(L), r(R), v(V) {}
bool operator<(const node& o) const //重载运算符
{
return l < o.l;
}
};
IT split(int pos)
{
IT it = s.lower_bound(node(pos)); //找到首个不小于pos的set
if (it != s.end() && it->l == pos) //无需,直接返回
return it;
--it; //否则一定在前一个区间中
int L = it->l, R = it->r; //【l,r】就是要分裂的区间
LL V = it->v; //取出值
s.erase(it); //删除原集合
s.insert(node(L, pos-1, V)); //构建前半段的新结合
return s.insert(node(pos, R, V)).first; //构建后半段的新集合并且返回地址
}
void assign_val(int l, int r, LL val)
{
IT itl = split(l),itr = split(r+1); //求出要被摊平区间的收尾地址
s.erase(itl, itr); //删除原集合
s.insert(node(l, r, val)); //添加新集合
}
void add(int l, int r, LL val) //区间加
{
IT itl = split(l),itr = split(r+1);
for (; itl != itr; ++itl)
itl->v += val;
}
LL rank(int l, int r, int k) //区间第k小
{
vector<pair<LL, int> > vp;
IT itl = split(l),itr = split(r+1);
vp.clear();
for (; itl != itr; ++itl)
vp.push_back(pair<LL,int>(itl->v, itl->r - itl->l + 1));
sort(vp.begin(), vp.end());
for (vector<pair<LL,int> >::iterator it=vp.begin();it!=vp.end();++it)
{
k -= it->second;
if (k <= 0)
return it->first;
}
}
LL pown(LL a, LL b, LL mod) //求区间幂次和
{
LL res = 1;
LL ans = a % mod;
while (b)
{
if (b&1)
res = res * ans % mod;
ans = ans * ans % mod;
b>>=1;
}
return res;
}
LL sum(int l, int r, int ex, int mod)
{
IT itl = split(l),itr = split(r+1);
LL res = 0;
for (; itl != itr; ++itl)
res = (res + (LL)(itl->r - itl->l + 1) * pown(itl->v, LL(ex), LL(mod))) % mod;
return res;
}
int n, m;
LL seed, vmax;
LL rd()
{
LL ret = seed;
seed = (seed * 7 + 13) % MOD7;
return ret;
}
LL a[imax_n];
int main()
{
cin>>n>>m>>seed>>vmax;
for (int i=1; i<=n; ++i)
{
a[i] = (rd() % vmax) + 1;
s.insert(node(i,i,a[i]));
}
s.insert(node(n+1, n+1, 0));
int lines = 0;
for (int i =1; i <= m; ++i)
{
int op = int(rd() % 4) + 1;
int l = int(rd() % n) + 1;
int r = int(rd() % n) + 1;
if (l > r)
swap(l,r);
int x, y;
if (op == 3)
x = int(rd() % (r-l+1)) + 1;
else
x = int(rd() % vmax) +1;
if (op == 4)
y = int(rd() % vmax) + 1;
if (op == 1)
add(l, r, LL(x));
else if (op == 2)
assign_val(l, r, LL(x));
else if (op == 3)
cout<<rank(l,r,x)<<endl;
else
cout<<sum(l,r,x,y)<<endl;
}
return 0;
}
十五.KD-tree:
O(sqrt(n))
求二维空间最近点对
inline int cmp(arr a,arr b){return a.d[D]<b.d[D]||a.d[D]==b.d[D]&&a.d[D^1]<b.d[D^1];}
inline void up(int k,int s)
{
a[k].min[0]=min(a[k].min[0],a[s].min[0]);
a[k].max[0]=max(a[k].max[0],a[s].max[0]);
a[k].min[1]=min(a[k].min[1],a[s].min[1]);
a[k].max[1]=max(a[k].max[1],a[s].max[1]);
}
int build(int l,int r,int dd)
{
D=dd;int mid=(l+r)>>1;
nth_element(a+l+1,a+mid+1,a+r+1,cmp);
a[mid].min[0]=a[mid].max[0]=a[mid].d[0];
a[mid].min[1]=a[mid].max[1]=a[mid].d[1];
if (l!=mid) a[mid].l=build(l,mid-1,dd^1);
if (mid!=r) a[mid].r=build(mid+1,r,dd^1);
if (a[mid].l) up(mid,a[mid].l);
if (a[mid].r) up(mid,a[mid].r);
return mid;
}
void insert(int k)
{
int p=root;D=0;
while (1)
{
up(p,k);
if (a[k].d[D]<=a[p].d[D]){if (!a[p].l) {a[p].l=k;return;} p=a[p].l;}
else {if (!a[p].r) {a[p].r=k;return;} p=a[p].r;}
D^=1;
}
}
int getdis(int k)
{
int res=0;
if (x<a[k].min[0]) res+=a[k].min[0]-x;
if (x>a[k].max[0]) res+=x-a[k].max[0];
if (y<a[k].min[1]) res+=a[k].min[1]-y;
if (y>a[k].max[1]) res+=y-a[k].max[1];
return res;
}
void ask(int k) //查询与(x,y)最近的点(曼哈顿距离)与其的距离
{
int d0=abs(a[k].d[0]-x)+abs(a[k].d[1]-y);
if (d0<ans) ans=d0;
int dl=(a[k].l)?getdis(a[k].l):INF;
int dr=(a[k].r)?getdis(a[k].r):INF;
if (dl<dr){if (dl<ans) ask(a[k].l);if (dr<ans) ask(a[k].r);}
else {if (dr<ans) ask(a[k].r);if (dl<ans) ask(a[k].l);}
}
十六.Trie字典树
用于维护字符串前缀的一种树,详见字符串总结
十七.LCT(link-cut-tree)
LCT用来维护动态的森林,以及一些链上操作,是处理节点无序的有根树组成的森林,进行一些列操作(例如合并,剪切,翻转,更新·····)
struct node{ //定义结点
int fa,ch[2]; //父亲和左右儿子。
bool reverse,is_root; //区间反转标记、是否是所在Splay的根
}T[maxn];
int getson(int x){
return x==T[T[x].fa].ch[1];
}
void pushreverse(int x){
if(!x)return;
swap(T[x].ch[0],T[x].ch[1]);
T[x].reverse^=1;
}
void pushdown(int x){
if(T[x].reverse){
pushreverse(T[x].ch[0]);
pushreverse(T[x].ch[1]);
T[x].reverse=false;
}
}
void rotate(int x){
if(T[x].is_root)return;
int k=getson(x),fa=T[x].fa;
int fafa=T[fa].fa;
pushdown(fa);pushdown(x); //先要下传标记
T[fa].ch[k]=T[x].ch[k^1];
if(T[x].ch[k^1])T[T[x].ch[k^1]].fa=fa;
T[x].ch[k^1]=fa;
T[fa].fa=x;
T[x].fa=fafa;
if(!T[fa].is_root)T[fafa].ch[fa==T[fafa].ch[1]]=x;
else T[x].is_root=true,T[fa].is_root=false;
//update(fa);update(x); //如果维护了信息,就要更新节点
}
void push(int x){
if(!T[x].is_root)push(T[x].fa);
pushdown(x);
}
void Splay(int x){
push(x); //在Splay到根之前,必须先传完反转标记
for(int fa;!T[x].is_root;rotate(x)){
if(!T[fa=T[x].fa].is_root){
rotate((getson(x)==getson(fa))?fa:x);
}
}
}
void access(int x){ //访问x结点,专门开辟一条x到根的路径
int y=0;
do{
Splay(x);
T[T[x].ch[1]].is_root=true;
T[T[x].ch[1]=y].is_root=false;
//update(x); //如果维护了信息记得更新。
x=T[y=x].fa;
}while(x);
}
void mroot(int x){ // 把x点变成树根
access(x);
Splay(x);
pushreverse(x);
}
void link(int u,int v){ // 连接两棵LCT
mroot(u);
T[u].fa=v;
}
void cut(int u,int v) // 分离出两棵LCT
mroot(u); //先把u变成根
access(v);Splay(v); //连接u、v
pushdown(v); //先下传标记
T[u].fa=T[v].ch[0]=0;
//v的左孩子表示v上方相连的重链
//update(v); //记得维护信息
}
十八.序列
1.dfs序
按照深度优先遍历树的顺序构成的数列
常见用法:
1.判断一个点是否是另一个点的子节点
2.找出一个点的第一个子节点
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
vector<int> g[100010];
int dfs_[200020],len;
void dfs(int u,int fa)
{
dfs_[++len]=u;
int sz=g[u].size();
for(int i=0;i<sz;i++)
{
if(g[u][i]!=fa)
{
dfs(g[u][i],u);
}
}
}
int main()
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
int from,to;
scanf("%d%d",&from,&to);
g[from].push_back(to);
g[to].push_back(from);
}
dfs(1,0);
for(int i=1;i<=len;i++)
{
printf("%d ",dfs_[i]);
}
printf("\n");
}
2.欧拉序
从根结点出发,按dfs的顺序在绕回原点所经过所有点的顺序
常见用法:
1.求LCA
2.求子树权值之和
#include<cmath>
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
vector<int> g[40010];
int len,a[80020];
void dfs(int u,int fa)
{
a[++len]=u;
int sz=g[u].size();
for(int i=0; i<sz; i++)
{
if(g[u][i]!=fa)
{
dfs(g[u][i],u);
a[++len]=u;
}
}
}
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
int n,m;
len=0;
memset(a,0,sizeof(a));
scanf("%d",&n);
for(int i=1; i<=n; i++)
{
g[i].clear();
}
for(int i=1; i<=n-1; i++)
{
int from,to;
scanf("%d%d",&from,&to);
g[from].push_back(to);
g[to].push_back(from);
}
dfs(1,0);
for(int i=1;i<=len;i++)
{
printf("%d ",a[i]);
}
}
}
十九.树链剖分
常用方法:
1.在一棵树上进行路径的修改、求极值、求和
2.通常将树剖分后变成区间问题,再用线段树处理
const int MAXN = (100000 << 2) + 10;
//Heavy-light Decomposition STARTS FORM HERE
int siz[MAXN];//number of son
int top[MAXN];//top of the heavy link
int son[MAXN];//heavy son of the node
int dep[MAXN];//depth of the node
int faz[MAXN];//father of the node
int tid[MAXN];//ID -> DFSID
int rnk[MAXN];//DFSID -> ID
void dfs1(int u, int father, int depth) {
/*
* u: 当前结点
* father: 父亲结点
* depth: 深度
*/
// 更新dep、faz、siz数组
dep[u] = depth;
faz[u] = father;
siz[u] = 1;
// 遍历所有和当前结点连接的结点
for (int i = head[u]; i; i = edg[i].next) {
int v = edg[i].to;
// 如果连接的结点是当前结点的父亲结点,则不处理
if (v != faz[u]) {
dfs1(v, u, depth + 1);
// 收敛的时候将当前结点的siz加上子结点的siz
siz[u] += siz[v];
// 如果没有设置过重结点son或者子结点v的siz大于之前记录的重结点son,则进行更新
if (son[u] == -1 || siz[v] > siz[son[u]]) {
son[u] = v;
}
}
}
}
void dfs2(int u, int t) {
/**
* u:当前结点
* t:起始的重结点
*/
top[u] = t; // 设置当前结点的起点为t
tid[u] = cnt; // 设置当前结点的dfs执行序号
rnk[cnt] = u; // 设置dfs序号对应成当前结点
cnt++;
// 如果当前结点没有处在重链上,则不处理
if (son[u] == -1) {
return;
}
// 将这条重链上的所有的结点都设置成起始的重结点
dfs2(son[u], t);
// 遍历所有和当前结点连接的结点
for (int i = head[u]; i; i = edg[i].next) {
int v = edg[i].to;
// 如果连接结点不是当前结点的重子结点并且也不是u的父亲结点,则将其的top设置成自己,进一步递归
if (v != son[u] && v != faz[u]){
dfs2(v, v);
}
}
}
INT64 query_path(int x, int y) {
/**
* x:结点x
* y:结点y
* 查询结点x到结点y的路径和
*/
INT64 ans = 0;
int fx = top[x], fy = top[y];
// 直到x和y两个结点所在链的起始结点相等才表明找到了LCA
while (fx != fy) {
if (dep[fx] >= dep[fy]) {
// 已经计算了从x到其链中起始结点的路径和
ans += query(1, tid[fx], tid[x]);
// 将x设置成起始结点的父亲结点,走轻边,继续循环
x = faz[fx];
} else {
ans += query(1, tid[fy], tid[y]);
y = faz[fy];
}
fx = top[x], fy = top[y];
}
// 即便找到了LCA,但是前面也只是分别计算了从一开始到最终停止的位置和路径和
// 如果两个结点不一样,表明仍然需要计算两个结点到LCA的路径和
if (x != y) {
if (tid[x] < tid[y]) {
ans += query(1, tid[x], tid[y]);
} else {
ans += query(1, tid[y], tid[x]);
}
} else ans += query(1, tid[x], tid[y]);
return ans;
}
void update_path(int x, int y, int z) {
/**
* x:结点x
* y:结点y
* z:需要加上的值
* 更新结点x到结点y的值
*/
int fx = top[x], fy = top[y];
while(fx != fy) {
if (dep[fx] > dep[fy]) {
update(1, tid[fx],tid[x], z);
x = faz[fx];
} else {
update(1, tid[fy], tid[y], z);
y = faz[fy];
}
fx = top[x], fy = top[y];
}
if (x != y)
if (tid[x] < tid[y]) update(1, tid[x], tid[y], z);
else update(1, tid[y], tid[x], z);
else update(1, tid[x], tid[y], z);
}
INT64 query(int i, int x, int y) {
/**
* 查询操作
*/
int lc = i << 1, rc = (i << 1) + 1;
if (x <= tree[i].left && tree[i].right <= y)
return tree[i].val;
if (tree[i].left > y || tree[i].right < x)
return 0;
if (tag[i]) pushdown(i);
return query(lc, x, y) + query(rc, x, y);
}
二十.根号算法
1.分块算法:
O(sqrt(n))
本质还是暴力,是一种对暴力的优化
处理区间问题,在线询问,可在一些情况代替线段树等区间数据结构
#include <bits/stdc++.h>
using namespace std;
const int maxn=1e5+7;
int block,belong[maxn],l[maxn],r[maxn];
long long a[maxn],Max[maxn];
int n;
void build() //建块,通用模板,任何题目都一样
{
block=sqrt(n); //每块大小
num=n/block; if(n%block) num++; //分块个数
for(int i=1;i<=num;i++)
{
l[i]=(i-1)/block+1; //l[i]表示i块的左端位置
r[i]=i*block; //r[i]表示i块的右端位置
}
r[num]=n;
for(int i=1;i<=n;i++)
belong[i]=(i-1)/block+1; //i属于哪一个块
for(int i=1;i<=n;i++)
for(int j=l[i];j<=r[i];j++)
Max[i]=max(Max[i],a[j]); //初始化,保存每块最大值,具体问题具体修改
}
void update(int x,int y) //单点更新,具体问题具体修改
{
a[x]+=y;
Max[belong[x]]=max(Max[belong[x],a[x]);
}
long long query(int x,int y) //区间查询,具体问题具体修改
{
long long ans=0;
if(belong[x]==belong[y]) //若x和y在同一块中,直接遍历块中全部元素求最大值
{
for(int i=x;i<=y;i++)
ans=max(a[i],ans);
return ans;
}
for(int i=x;i<=r[belong[x]];i++) //查询x到所在块最右端
ans=max(ans,a[i]);
for(int i=belong[x]+1;i<belong[y];i++) //查询x与y之间的完整块
ans=max(ans,Max[i]);
for(int i=l[belong[y];i<=y;i++) //查询y所在块的左端到y
ans=max(ans,a[i]);
return ans;
}
2.莫队算法:
本质仍是暴力,处理区间问题,适用于只询问不修改,可以离线查询,在已知[l,r]的情况,可以在O(1)内推出[l,r-1],[l,r+1],[l+1,r],[l-1,r]的问题
#include <bits/stdc++.h>
using namespace std;
const int maxn=1e6+7;
struct node
{
int l,r,id;
}Q[maxn]; //储存询问
int pos[maxn]; //记录每个点所在块的编号
long long ans[maxn]; //记录每个点的答案
long long flag[maxn];
int a[maxn];
bool cmp(node a,node b)
{
if(pos[a.l]==pos[b.l])
return a.r<b.r;
return pos[a.l]<pos[b.l];
}
int n,m,k;
int L=1,R=0,Ans=0;
void add(int x) //具体问题具体修改
{
}
void del(int x)
{
}
int main()
{
cin>>n>>m>>k;
int sz=sqrt(n); //块的大小
for(int i=1;i<=n;i++) //读入数据
{
cin>>a[i];
.................
pos[i]=i/sz;
}
for(int i=1;i<=m;i++) //读入询问
{
cin>>Q[i].l>>Q[i].r;
Q[i].id=i;
}
sort(Q+1,Q+m+1,cmp); //离线询问,并排序
for(int i=1;i<=n;i++) //莫队算法
{
while(L<Q[i].l)
{
del(L-1);
L++;
}
while(L>Q[i].l)
{
L--;
add(L-1);
}
while(R<Q[i].r)
{
R++;
add(R);
}
while(R>Q[i].r)
{
del(R);
R--;
}
ans[Q[i].id]=Ans;
}
for(int i=1;i<=m;i++)
cout<<ans[i]<<endl;
return 0;
}
二十一.CDQ分治
https://blog.csdn.net/wu_tongtong/article/details/78785836
通过对时间复杂度加一个log实现对问题降维,常用来代替树套树等复杂数据结构,且优于其
适用条件:
修改操作对查询的贡献独立,修改操作互不影响,题目允许离线算法
解决三维偏序或四维偏序问题
步骤:
1.将操作序列分为两个长度相等的部分
2.递归处理前部分子问题
3.计算前部分子问题中的修改操作对后部分子问题的影响
4.递归处理后部分子问题
1.三维偏序:
把询问重复的去掉之后开始分治
第一维排序第二维分治第三维建树状数组然后对每个询问求前缀和就是比他小的询问的数量
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cstdio>
#define rep(i,l,r) for (int i=l;i<=r;i++)
#define down(i,l,r) for (int i=l;i>=r;i--)
#define clr(x,y) memset(x,y,sizeof(x))
#define maxn 200500
using namespace std;
struct data
{
int x,y,z,ans,s;
}a[maxn],q[maxn];
int t[maxn],ans[maxn],n,m;
bool cmp(data a,data b){
if (a.x==b.x&&a.y==b.y) return a.z<b.z;
else if (a.x==b.x) return a.y<b.y;
else return a.x<b.x;
}
int lowbit(int x){ //树状数组相关
return (x&(-x));
}
void add(int x,int y){
while (x<=m){
t[x]+=y; x+=lowbit(x);
}
}
int query(int x){
int ans=0;
while (x){
ans+=t[x]; x-=lowbit(x);
}
return ans;
}
bool cmp2(data a,data b){
if (a.y==b.y) return a.z<b.z;
else return a.y<b.y;
}
void cdq(int l,int r){
if (l==r) return;
int mid=(l+r)/2;
cdq(l,mid); cdq(mid+1,r);
sort(q+l,q+1+mid,cmp2); sort(q+1+mid,q+r+1,cmp2);
int l1=l,l2=mid+1;
while (l2<=r)
{
while (l1<=mid&&q[l1].y<=q[l2].y)
{
add(q[l1].z,q[l1].s);
l1++;
}
q[l2].ans+=query(q[l2].z);
l2++;
}
for(int i=l;i<l1;i++)
add(q[i].z,-q[i].s);
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
cin>>a[i].x>>a[i].y>>a[i].z; //读入数据
sort(a+1,a+1+n,cmp); //一维排序
int cnt=0;
int top=0;
for(int i=1;i<=n;i++)
{
cnt++;
if (a[i].x!=a[i+1].x||a[i].y!=a[i+1].y||a[i].z!=a[i+1].z) //删除相同项
{
q[++top]=a[i];
q[top].s=cnt;
cnt=0;
}
}
cdq(1,top);
for(int i=1;i<=top;i++)
ans[q[i].ans+q[i].s-1]+=q[i].s;
for(int i=0;i<n;i++)
cout<<ans[i]<<endl;
return 0;
}
2.四维偏序
具体思想同上
#include <cstdio>
#include <cstring>
#include <algorithm>
#define rec(i, x, n) for(int i = x; i <= n; i++)
const int maxn = 200005, inf = 0x3f3f3f3f;
int n, tot, rank[maxn], tr[maxn << 2];
struct _mat {
int a, b, c, d, ans; // a up, b up, c down, d down
} c[maxn], tmp[maxn];
inline int iread() {
int f = 1, x = 0; char ch = getchar();
for(; ch < '0' || ch > '9'; ch = getchar()) f = ch == '-' ? -1 : 1;
for(; ch >= '0' && ch <= '9'; ch = getchar()) x = x * 10 + ch - '0';
return f * x;
}
void pushup(int p) {
tr[p] = std::min(tr[p << 1], tr[p << 1 | 1]);
}
bool cmp1(_mat x, _mat y) {return x.c > y.c;}
bool cmp2(_mat x, _mat y) {return x.d > y.d;}
void change(int p, int l, int r, int x, int w) {
if(l == r && r == x) {
tr[p] = w;
return;
}
int mid = l + r >> 1;
if(x <= mid) change(p << 1, l, mid, x, w);
else change(p << 1 | 1, mid + 1, r, x, w);
pushup(p);
}
int query(int p, int l, int r, int x, int y) {
if(x > y) return inf;
if(x <= l && r <= y) return tr[p];
int mid = l + r >> 1;
int res = inf;
if(x <= mid)
res = std::min(res, query(p << 1, l, mid, x, y));
if(y > mid)
res = std::min(res, query(p << 1 | 1, mid + 1, r, x, y));
return res;
}
void cdq(int l, int r) {
if(l == r) return;
int mid = l + r >> 1, q1 = l, q2 = mid;
rec(i, l, r) tmp[c[i].c <= mid ? q1++ : ++q2] = c[i];
rec(i, l, r) c[i] = tmp[i];
for(int i = mid + 1, j = l; i <= r; i++) {
for(; j <= mid && c[j].d > c[i].d; j++) change(1, 1, tot, c[j].a, c[j].b);
if(c[i].b > query(1, 1, tot, 1, c[i].a - 1)) c[i].ans = 1;
}
for(int i = l; i <= mid && c[i].d > c[r].d; i++) change(1, 1, tot, c[i].a, inf);
cdq(l, mid); cdq(mid + 1, r);
}
int main() {
n = iread();
rec(i, 1, n) {
int x1 = iread(), y1 = iread(), x2 = iread(), y2 = iread();
c[i] = (_mat){rank[i] = x1, y1, x2, y2};
}
std::sort(rank + 1, rank + 1 + n);
tot = std::unique(rank + 1, rank + 1 + n) - rank - 1;
std::sort(c + 1, c + 1 + n, cmp1);
rec(i, 1, n) c[i].c = i, c[i].a = std::lower_bound(rank + 1, rank + 1 + tot, c[i].a) - rank;
std::sort(c + 1, c + 1 + n, cmp2);
memset(tr, 0x3f, sizeof(tr));
cdq(1, n);
int ans = 0;
rec(i, 1, n) ans += c[i].ans;
printf("%d\n", ans);
return 0;
}