qbxt-数据结构

版权声明:如需转载请联系博主! From 博主Haolin https://blog.csdn.net/ohh_haolin/article/details/86658073

堆 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])
开车旅行

https://www.luogu.org/problemnew/show/P1081d

LCA

  • 通过倍增计算
  1. 把k和y跳到同一层,深度一样
    把步数拆成用2进制的模式,预处理能跳到哪里就ok
  2. 一起向上走

代码

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的字串长度完全相同

  • 思路:
  1. 把字符串改成26进制的数字,对于10^18取模,得到n-k+1个10 ^18 级别的数
    处理的时候减去开头的,把剩下来的乘以26,再加上最后一个的数
  2. 把10^18级别的数映射到10 ^5次上
  • O(S)

二位哈希

  1. 把行压缩
  2. 把列压缩
  3. 把压缩过的再压缩

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,R
    for(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、线段树等)来维护每一条链。
  • 重要性质:重链上的所有点都是连续的!!意味着可以把树连变成连续区间

https://www.cnblogs.com/ivanovcraft/p/9019090.html

  • 步骤:
    1. 第一遍dfs求出树每个结点的深度deep[x],其为根的子树大小size[x],以及每个点的父亲fa[x],和重儿子son[]。
    2. 第二遍沿着重儿子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万)以内可以用

  • 本身有排序的功能

  • 可以访问不存在的元素!返回一个空值

  • 函数:

    1. 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,数组也可以

猜你喜欢

转载自blog.csdn.net/ohh_haolin/article/details/86658073