清北学堂noip2019集训D2——数据结构

基本数据结构

  • STL:stack
  • 定义:stack a;
  • 查询堆顶:a.top();
  • 压入栈顶:a.pop();
  • 查询a中的元素个数:a.size();
  • 清空只能慢慢pop。
例题1
  • 给定一个栈,维护三个操作。
    1:将一个数x压入栈中。
    2:求栈中数的最大值。
    3:弹出栈顶的数。
    Q<=1000。
    Q<=100000。
例题2
  • 给定一个栈,维护四个操作。
    1:将一个数x压入栈中。
    2:求栈中数的最大值。
    3:弹出栈顶的数。
    4:求栈中的栈底开始的最大前缀和。
    Q<=1000。
    Q<=100000。
例题3
  • 给定一个栈,维护五个操作。
    1:将一个数x压入栈中。
    2:求栈中数的最大值。
    3:弹出栈顶的数。
    4:求栈中的栈底开始的最大前缀和。
    5:求栈中的栈顶开始的最大前缀和。  总和-栈底开始的最小前缀和
    Q<=1000。
    Q<=100000。
例题4
  • 给定一个数列,维护五个操作。
    1:在光标的后面插入一个数字。
    2:删除光标前的最后一个数字。
    3:左移光标。
    4:右移光标。
    5:求光标前的最大前缀和。
    Q<=1000。
    Q<=100000。
栈的一些其它应用
  • 有n个数排成一列,每次可以选择最前面的数压入栈,或者弹出栈顶的元素。
    求不同的出栈序列方案总数。
    例如当n=3时共有5个不同的出栈序列方案:123,132,213,231,321.
  • 卡特兰数!

队列

  • queue:
  • 定义:queue a;
    插入队尾:a.push(x);
    删除队首:a.pop();
    查询队尾:a.back();
    查询队首:a.front();
    查询长度:a.size();
    清空只能慢慢pop。
  • deque:
  • 定义:deque a;
    插入队尾:a.push_back(x);
    插入队首:a.push_front(x);
    删除队首:a.pop_front();
    删除队尾:a.pop_back();
    查询队尾:a.back();
    查询队首:a.front();
    查询长度:a.size();
    清空:a.clear();
例题1
  • 给定一个队列,维护三个操作。
    1:将一个数x压入队列中。
    2:求队列中数的最大值。
    3:弹出队列的数。
    Q<=1000。
    Q<=100000。
include <bits/stdc++.h>
using namespace std;
int main()
{
	deque <int> a,b; //a值,b时间戳 
	for (i=1; i<=Q; i++)
	{
		cin>>A;
		if (A==1)  //每执行A==1 都会使a.size+1 
		{
			cntI++;
			cin>>x;
			while (a.back()<=x) a.pop_back(),b.pop_back();  //每执行这个while,都会使a.size减-1 
			a.push_back(x);
			b.push_back(cntI);
		}
		if (A==2) cout<<a.front()<<endl;
		if (A==3)
		{
			cntD++;
			if (cntD == b.front()) a.pop_front(),b.pop_front();
		}
	} 
	return 0; 
} 
O(Q) 

单调队列

  • 在每次进入队伍时,判断当前元素与队尾元素的大小关系,若小于队尾元素,则队尾不可能成为最小值,直接删除即可。
    每次查询的答案一定在队首位置。
    由于每个元素至多进队列和出队列一次,时间复杂度为O(n)。
例题1
  • 给定一个队列,维护五个操作。
    1:将一个数x压入队列中。
    2:求队列中数的最大值。
    3:弹出队列的数。
    4:求队列中的最大前缀和。
    5:求队列中的最大后缀和。 = 总和 - 最小前缀和
    Q<=1000。
    Q<=100000。
例题2——广告印刷
  • 有n个数ai。从中选出一段区间[L,R],使得(R-L+1)*min{a_L,…,a_R}最大。
    n<=100000。
  • **思路:**枚举最小值a[i],之后向右找最远扩展到哪里,右边最近的且比a[i]小的
  • 对于每个i,都在右边找一个最近且比a[i]小的。 这件事情可以用单调队列解决。
  • 考虑对于第i个数,求出当这个数成为最小值时,往左往右分别最远能到哪里。
    用这些来更新答案就可以了。
    使用单调队列来实现这一过程。
例题3
  • 给定n*m的01矩阵
    找一个面积最大的全0子矩阵。
    n , m ≤ 1000 n,m\leq1000 n,m1000
    if (a[i][j]==0) f[i][j] = f[i-1][j] + 1; else f[i][j] = 0;
    

RMQ问题

  • 给定n个数,有Q次询问,每次询问一段区间的最小值。

  • 倍增。 nlgn 1
    线段树。 n lgn
    分块。 nsqrtn sqrtn
    整体二分。

  • 倍增:记录所有长度为2的幂次的区间的最小值。
    n=5 [1,1] [2,2] [3,3] [4,4] [5,5] [1,2] [2,3] [3,4] [4,5] [1,4] [2,5]
    f[i][j] 表示 [i,i+2^j-1] 这段区间的最小值 [2,5] f[2][2] nlgn项

    f[i][0] = a[i]     f[i][j]表示[i,i+2^j-1]这段区间的最小值
    f[i][j] = min(f[i][j-1],f[i+(1<<j-1)][j-1])
    for (j=1; j<=20; j++)
      for (i=1; i<=n-(1<<j)+1; i++)
         f[i][j]=min(f[i][j-1],f[i+(1<<j-1)][j-1])
    
    [L,R]  先找到一个k,使得2^k <= R-L+12^(k+1) > R-L+1
    一开始预处理 p[x] 表示当R-L+1=x时k应该是多少
    k=int(log(R-L+1)/log(2))
    k=p[R-L+1];
    min(f[L][k],f[R-(1<<k)+1][k])       开车旅行
    
    
  • 我们考虑记录a[x]~a[x+2^k-1]的最小值,令其为f[x][k]。这可以在nlgn的时间内求出。
    对于每次询问L~R,令k=log[R-L+1]。有ans=min(f[L][k],f[R-2^k+1][k])。(即进行O(1)询问)

LCA

  • 给定一棵树,求两个点的最近公共祖先。
    通过倍增来求LCA。

  • 思路:

    1.将两个点深度变成一样。

  1. 一起往上走找最近公共祖先。
  • 事先得先求出每个点的父亲是谁,以及它的深度是多少。

  • dfs

  • fa[i]表示i的父亲,dep[i]表示i的深度
    第一步:将x和y走到同一层。
    if (dep[x]<dep[y]) swap(x,y); // 保证dep[x]>=dep[y]
    意味着 x要向上走dep[x]-dep[y]步。

  • f[i][j] 表示 i向上走2^j步后,能到哪儿
    f[i][0]=fa[i]; f[i][j]=f[f[i][j-1]][j-1]
    for (j=1; j<=20; j++) for (i=1; i<=n; i++) f[i][j]=f[f[i][j-1]][j-1]
    通过f数组来加速x向上走这个过程。

  • x向上走dep[x]-dep[y]步。
    t=dep[x]-dep[y] 将t二进制分解 10 = 1010
    x先向上跳8步,再向上跳2步
    for (i=20; i>=0; i–) if (t&(1<<i)) x=f[x][i];
    x和y就会到达同一层
    让x和y一起往上跳,找它们的最近公共祖先

    for (i=20; i>=0; i--)
      if (f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i] 
    
    if (x==y) return x; else return f[x][0];
    
    
  • 预处理fa,dep

    f[i][0]=fa[i];  f[i][j]=f[f[i][j-1]][j-1]
    for (j=1; j<=20; j++) for (i=1; i<=n; i++) f[i][j]=f[f[i][j-1]][j-1]
    
    t=dep[x]-dep[y] 
    for (i=20; i>=0; i--) if (t&(1<<i)) x=f[x][i];  //这一步结束时有可能x==y
    if (x==y) return x;
    for (i=20; i>=0; i--)
      if (f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i] 
    return f[x][0];
    
    
  • dfs

    void dfs(int x,int y)
    {
      dep[x]=y;
      for (i=son of x) {fa[i]=x; dfs(i,y+1);}
    } 
    dfs(1,1);
    
    
链上最大值问题
  • 给定一棵带边权的树,每次询问两个点x,y,求这两个点的路径上最长的边是多少。

  • 思路: f[i][j]从i出发向上走2^j步能走到哪儿
    g[i][j]从i出发向上走2^j步的过程中经过的最长边是什么

  • f[i][0]=fa[i],g[i][0]=dis(i,fa[i]);
    f[i][j]=f[f[i][j-1]][j-1] g[i][j]=max(g[i][j-1],g[f[i][j-1]][j-1]).
    求答案的时候,用g数组来更新最大值

链上和问题
  • 给定一棵树,每次询问两个点x,y,求这条路径上边权和是多少。
  • 思路: dis[i] 从根节点出发到i经过的边的长度和
    x~y的路径长度 dis[x]+dis[y] – 2*dis[LCA(x,y)]
链上异或问题
  • 给定一棵树,每次询问两个点x,y,求这条路径上边权异或和是多少。
  • 思路: dis[i]表示从1出发到i经过的边的边权亦或和
    x~y = dis[x]^dis[y]

Hash

简单Hash

  • 有100个数字,每个数字的大小都是 ≤ 1 0 5 \leq10^5 105
    问是否存在一对数字的值相等。
    要求一个线性做法。
    for (i=1; i<=100; i++)
    {
      cin>>A; f[A]++;
      if (f[A]==2) return true;
    }
    return false;
    
    
  • ≤ 1 0 9 \leq10^9 109 呢?
    for (i=1; i<=100; i++)
    {
      cin>>A; A%=12345678; f[A]++;
      if (f[A]==2) return true;
    }
    return false;
    
    
重要性质
  • 假如有n个自然数。
    要使得这n个数之间在大概率下不冲突(不同的数在模p意义下相等)。
    p>=n^2。

一维Hash

void Insert(int x)
{
  int t=x % 19999997;  //a[i]来表示i这个位置存的数是什么
  while (a[t] && a[t]!=x) t=(t+1) % 19999997;
  a[t]=x; b[t]++;  // b[i]表示a[i]这个数字出现了几次
}
int Query(int x)
{
  int t=x % 19999997;
  while (a[t] && a[t]!=x) t=(t+1) % 19999997;
  if (a[t]==x) return b[t]; else return 0;
}
cin>>A; Insert(A);
if (Query(A)==2) return true;

  • 模数开成10倍的元素个数,近似线性

字符串Hash

  • 给定一个字符串,求是否存在两个长度为k的子串完全相同。
  • abcabc k=4
  • 要求 O ( ∣ s ∣ ) O(|s|) O(s)
  • |s|<=100W 对于每一个数字 先对10^18取模
    得到 n-k+1个 10^18级别的数,询问是否存在一对数相同
    两次hash 第一次hash -> 很大的26进制数 变成 10^18级别的数
    二次 -> 10^18级别的数映射到1000W级别的数组

二维Hash

  • 给定一个nn的矩阵,求是否存在两个完全相同的kk的子矩阵。
    要求时间复杂度与读入同阶。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值