数据结构
堆 stack
stl之中的东东
- stl函数
#nclude <bits/stdc++>
stack<int> a;
a.top();
a.push();
a.pop(); //注意!如果a为空再弹或者取出元素会爆炸
a.size();
while(a.size()){
a.pop();
}
while(!a.empty){
a.pop();
}
堆列 queue
stl简单实现
- st函数
#nclude <bits/stdc++>
queue<int> a;
a.pop();
a.push_back();
a.front(); //注意!如果a为空再弹或者取出元素会爆炸
a.back();
a.size();
while(a.size()){
a.pop();
}
while(!a.empty()){
a.pop();
}
deque
deque<int> a;
a.push_front();
a.pop_front();
a.clear();
单调队列
压入x,如果x大于头元素,则清空;如果x没有大于队头,那么压入队尾。
维护的队列是队头最大,队尾最小
优化一下:使用双端队列:代码如下
1 压入队列
2 求出最大值
3 维护当前划窗最大值
deque<int> a,b; //a值,b为时间戳
for(int i=1; i<=q; i++){
cin >> A;
if(A==1){
cin >> x;
cntI++;
while(a.back()<=x) a.pop_back(),b.pop_back();
a.push_back(x);
b.push_back(cntI);
}
if(a==2) cout << a.front() << endl;
if(A==3){
antD++;
if(cntD==b.front())
a.pop_front(),b.pop_front();
}
}
广告印刷
有n个数ai,从中选取一段区间[L,R],使得(R-L+1)×min{a[L]……a[R]}
最大
n<=100000
- 思路:固定最小值,尽量让r-l+1大。
枚举i,从i出发向左向右出发为j,满足a[i] < a[j],找最远能到哪里
用单调队列记录从这个点开始的单调队列,不要弹出,维护最小值。
全0矩阵
给定一个n*m的01矩阵
找一个面积最大的全零矩阵
m,m<=1000
- 思路:固定下方的变,搜索每一点向上有几个点,枚举区间,维护单调队列,维护最大值
倍增
- 主要思想:
记录所有长度为2的幂次的区间最值。
用f[i][j] 表示 [i,i+2^j -1] 这个区间的最小值,空间上为nlogn
预处理:f[i][0] = a[i], f[i][j] = max(f[i][j-1], f[i+(1<< j-1)][j])
//预处理
for(int i=1;i<=n;i++)
f[i][0]=a[i];
//处理
for(int j=1;j<=20;j++)
for(int i=1;i<=n-(1<<j)+1;i++)
f[i][j] = max(f[i][j-1], f[i+1<<(j-1)][j-1]); //注意,位移运算是最后计算
//输出答案
//询问区间l,r
int k = log(R-L+1)/log(2);
cout << min(f[l][k],f[r-(1<<k)+1][k])
开车旅行
LCA
- 通过倍增计算
- 把k和y跳到同一层,深度一样
把步数拆成用2进制的模式,预处理能跳到哪里就ok - 一起向上走
代码
int lca(x,y){
//预处理
for(int i=1;i<=n;i++)
f[i][0]=fa[i];
for(int j=1;j<=n-(1<<j)+1;j++)
for(int i=1;i<=n;i++)
f[i][j] = f[f[i][j-1]] [j-1];
//跑dfs标记深度dep数组
do something
//跳到同一层
t = dep[x]-dep[y];
for(int i=20; i>=0; i--)
if(t&(1<<j))
x = f[x][i];
//一起向上走
if(x==y) return x; //如果x==y时候,本身就是LCA
//不是的化找爸爸
for(int i=20;i>=0;i++)
if(f[x][i]!=f[y][i]) //不一样的话向上跳
x = f[x][i],y = f[y][i];
else if(x==y) return x; //走到一起了
else f[x][0];//他们的爸爸就是他们的点
}
链上最大值问题
给定一个数,找出路径上的最长边
f[i][j]从i出发向上走2^j步能走到哪里
g[i][j]从i出发向上走的过程中的最长边是什么
代码类似于LCA
链上和问题
给定一棵树,每次询问两个点x,y,求这条路径上边权和是多少。
LCA维护
路上异或问题
用dis[i]表示从1出发到i的边全异或和
x~y = dis[x]^dis[y]
Hash
有100个数字,每个数字的大小都是<=10^9
问是否存在一对数字相等
要求O(N)做法
for(int i=1;i<=100;i++){
cin >> A;
A %=13248643; //随便一个大于N的平方的数
f[A]++;
if(f[A]==2) return true;
}
- 正解:
当超过100时候!
int mod = 10*N; //一定要把模数开成十倍的N会优化成线性!
void Insert(int x){
int t = x%mod; //a[i]来表达这个位置存的数字是什么
while(a[t]&&a[t]!=x)
t = (t+1)%mod;
a[t]=x; b[t]++; //b[i]表示这个数字出现几次
}
int query(int x){
int t = x%mod;
while(a[t]&&a[t]!=x)
t=(t+1)%mod;
if(a[t]==x) return b[t];
else return 0;
}
注意!一旦a被给了值就不能再修改了
字符串Hash
给定一个字符串,求是否存在两个长度为k的字串长度完全相同
- 思路:
- 把字符串改成26进制的数字,对于10^18取模,得到n-k+1个10 ^18 级别的数
处理的时候减去开头的,把剩下来的乘以26,再加上最后一个的数 - 把10^18级别的数映射到10 ^5次上
- O(S)
二位哈希
- 把行压缩
- 把列压缩
- 把压缩过的再压缩
CTSC 2014 qq企鹅
trie树
https://segmentfault.com/a/1190000008877595?utm_source=tag-newest
例题
给定n个相互不同的串,求存在对少对(i,j)(共n^2对)满足i是j的字串
- 思路:建立trie树,把末尾子午标记为red,dfs求出红色节点下面有几个节点就是符合几对(i,j)
例题
给n个数,求异或和最大
- 思路:尽量要高位为1
转二进制,建立trie树,枚举每个数,枚举每个数的异或最大的可能
并查集
一开始f[i]=i
当(u,v)存在的时候,f[u]=v
- 路径压缩,把爸爸的爸爸变成你的爸爸
O(nalpha(n))
int getf(int x){
return f[x]==x?x:f[x]=getf(f[x]);
}
校门外的树
学校门口有n个点(1~n),要种一堆树,种m次,每次在(li,ri)每个整数点种一颗树,每次种万后求有多少点还没有种上树
- O(n+m)
f[i]表示父亲,保证i的祖先是i及i的右边第一个没有种过树的点
对于一个操作L,Rfor(int i=getf(L);i<=R;i=getf(i+1)){ sum--;f[i]=getf(i+1); }
线段树
- 单点修改,单点查询
- 区间修改,单点查询
- 单点修改,区间查询
- 区间循环,区间查询
O(nlogn)
最多O(4logN)就可以表示任何一段区间
动态开点
//存某个节点的左儿子,右儿子,合
int ls[9999999],rs[99999999],sum[999999999],
//节点变化,节点左、右端点,要修改的点,增加的值
int cnt=0;
// 返回当前点的编号或者当前节点编号
int insert(&root,int l,int r,int t,int x){
if(!root) root=++cnt; //如果节点不存在那么新建节点
sum[root]+=x;
if(l==r) return;
int mid=l+r>>1;
//这里root已经++了!
if(t<=mid) insert(ls[root],l,mid,t,x);//自动维护!
else insert(rs[root],mid+1,r,t,x); //自动维护!!!
}
当有10^9的叶子,有10 ^5的操作的时候,普通线段树会炸空间;动态开点只需要10 ^5个点,不会炸空间
查询
int query(int &root,int l,int r,int x,int y){
if(l==x&&r==y) return sum[root];
int mid=(l+r)>>1;
if(x<=mid) int suml=query(ls[root],l,mid,x,mid(mid,y));
if(y>mid) int sumr=query(rs[root,mid+1,max(mid+1,y)]);
return suml+sumr;
}
打lazy标记–区间修改
void update(int &root,int l,int r,int p,int q,int x){
if(!root) root=++cnt;
sum[root]+=x*(l~r 和 p~q的交集);
if(l==r && r==q) {tag[root]+=x;return;}
int mid=l+r>>1;
if(p<=mid) update(ls[root],l,mid,p,q,x);
if(q>mid) update(rs[root],mid+1,r,max(mid+1,p),q,x);
}
打lazy标记–区间查询
int down(int x,int l,int r){ //下传标记
if(!ls[x]) ls[x]=++cnt;
if(!rs[x]) rs[x]=++cnt;
tag[ls[x]]+=tag[x];
tag[rs[x]]+=tag[x];
sum[ls[x]] += tag[x]* 左儿子节点个数;
sum[rs[x]] += tag[x]* 右儿子节点个数;
tag[x]=0;
}
int query(int &root,int l,int r,int x,int y){
if(l==x&&r==y) return sum[root];
down(root,l,r);
int mid=(l+r)>>1;
if(x<=mid) int suml=query(ls[root],l,mid,x,mid(mid,y));
if(y>mid) int sumr=query(rs[root,mid+1,max(mid+1,y)]);
return suml+sumr;
}
线段树2模板
GSS4
- 老师推荐把GSS的一系列题都看了
一开始有n个数,有q次操作,维护两种操作
1.将一段区间的树开根后向下取整
2.查询区间和
n,q<=100,000
- 思路:对于操作一,不停的找区间的最大值,如果最大值>1,则暴力进行单点更新操作,查询就是普通查询方法。
GSS1
一开始有n个数,共有q次操作,维护两种操作:1.修改第x个值ax变为y,2.查询这个x个数的最大字段和
n,q<=100,000
- 思路:对于区间一共有3种情况: 左兄弟节点的后缀和+自己的最大前缀和,自己,自己的最大后缀+右兄弟节点的最大前缀和。维护这三个值
树状数组
能维护带有和性质的结构,前缀查询
int sum[9999999];
inline int lowbit(int x){
return x&-x;
}
void query(int x){
int ans=0;
for(int i=x;i;i-=lowbit(x))
ans+=sum[i]
}
void update(int p,int x){
for(int i=x;i<=n;i+=lowbit(x))
sum[i]+=x;
}
二位树状数组
有一个nm矩阵,支持单点修改,矩阵查询和
n,m<=500
- 思路:把树状数组的for循环变成两个
void update(int x,int y,int z){
for(int i=x;i<=n;i+=lowbit(i))
for(int y;j<=m;j+=lowbit(j))
sum[i][j]+=z;
}
void query(int x,int y,int z){
// 把一维改二维
do something;
}
树剖
- 概念:树链剖分,指一种对树进行划分的算法,它先通过轻重边剖分将树分为多条链,保证每个点属于且只属于一条链,然后再通过数据结构(树状数组、SBT、SPLAY、线段树等)来维护每一条链。
- 重要性质:重链上的所有点都是连续的!!意味着可以把树连变成连续区间
- 步骤:
- 第一遍dfs求出树每个结点的深度deep[x],其为根的子树大小size[x],以及每个点的父亲fa[x],和重儿子son[]。
- 第二遍沿着重儿子dfs,记录下dfs序以及重链的开始点
五个值:fa,son,deep,size,dfs序,重链的开始点
void pre-dis(){
//构建线段树
do something;
}
void dfs1(int x,int ){ //当前到x节点,深度为y
dep[x]=y;siz[x]=1; //记录深度,初始化size(要加上那个他本身)
for(i in son of x){
fa[i]= x; //记录父亲
dfs1(i,y+1);
siz[x]+=siz[i]; //记录大小
}
}
void dfs2-pre(){
for(i in all point)
f[i]=i; //初始化f把自己设置自己
}
void dfs2(int x){ //现在在哪里
w[x]= ++cnt; //记录下dfs序
int t=the heavy son of x;
f[t]=f[x]; //通过f记录 重边的开始节点
dfs2(t);
for(i=son of x except t)
dfs(i)
}
- 作用:
1:把节点u权值改为t。
2:询问链上最大值。
3:询问链和。
4:求LCA
软件包管理器
给定一棵点数为n的树,初始时点权都为0,支持两种操作。
1:将一条链的点的点权变为1,输出改变前和改变后的点权总和的差值。
2:将一颗子树的点的点权变为0,输出改变前和改变后的点权总和的差值。
n<=100000。
Little Devil I
给定一棵边为黑白的树,初始时都为白,维护三种操作。
1:将一条链上的颜色取反。
2:将一条链上的相邻边取反。(仅有一个端点在该链上)
3:查询一条链的黑边数量。
n,Q<=100000。
STL
map
-
映射,把它看做一个无限大的数组。
-
定义方式:map<int ,int> a;
-
使用方式:a[x]++,cout<<a[y]等。
-
可以用秩访问
-
常数巨大的O(logN),100,000 (10万)以内可以用
-
本身有排序的功能
-
可以访问不存在的元素!返回一个空值
-
函数:
- a.clear();
#include<bits/stdc++>
using namespace std;
int main1(){
map<string ,int> a;
a["b"]=34;
cout << a[b] << endl;
return 0;
}
int main2(){
for(map<int,int> :: iterator sit=a.begin();sit!=a.end();sit++)
cout << sit->first << sit->second << endl;
}
set
-
入门
定义:set a;
插入元素:a.insert(100);
清空set:a.clear();
查询set中有多少元素:a.size();
查询首元素:a.begin(),返回iterator。
查询最后一个元素+1:a.end(),返回iterator。
要查询的时候要 *(–a.end())!!注意星号位置
删除:a.erase(sit); sit是一个iterator类型的指针或者数字,删除一个不存在的数字会返回1/0表示是否成功
判断是否有数字100:a.count(100)。 -
进阶
从小到大遍历set:
for (set<int>::iterator sit=a.begin(); sit!=a.end(); sit++)
cout<<*sit<<endl;
s.lower_bound(100),返回大于等于100且最小的元素的指针,返回类型为iterator,注意若没有大于等于100的元素,则返回的值随机。
s.upper_bound(100),与lower_bound类似,但返回的是大于不等于
调用小于且不等于的100的方法 *(–a.lower_bound(100))
推荐预插入最大值和最小值!这样子能够分辨是否是最大值和最小值
a.insert(100000000000);
a.insert(-9999999999999);
- 结构体的情况
重构运算符!!要用小于号
struct node{
int x,y;
};
bool operator < (const node& a, const node &b) {
return a.x<b.x;
}
例题: bzoj1204
multiset
可以出现多次
a.count()可以用来计数了
heap/priority_queue
二叉堆
在数组中使用的函数。
将第n个数放进堆中:push_heap(a+1,a+n+1);
将堆顶弹出堆:pop_heap(a+1,a+n+1);
构造整个堆:make_heap(a+1,a+n+1);
比较函数cmp
sort
nth_element
把第几小的排在第几位,但是会打散其他的东西
nth_element(a+1,a+k,a+n+1);
stl
- reverse(a+1,a+n+1) 反转
- random_shuffle(a+1,a+n+1) 随机排序
- next_permutation(a,a+1) 获得正序排列
- prev_permutation(a,a+5) 获得逆序全排列
- min_element(a,a+10) 返回的是指针
- max_element(a,a+35) 返回的是指针
- binanry_search(a+1,a+n+1,x) 要求a顺序,判断x是否在a中。
常用stl
min(x,y) 返回两者最小值
max(x,y) 返回两者最大值
swap(x,y) 交换x和y,数组也可以