Day3 数据结构

Vector

定义方式:vectora;

有时压入数字时会自动开原空间的两到三倍。

末尾压入容器:a.push_back(x);
在末尾弹出容器:a.pop_back();
清空容器:a.clear();
查询元素个数:a.size();
首指针:a.begin();
插入元素在sit位置:a.insert(sit,x);其中sit是vector的迭代器。

Stack-先进后出

在末尾压入容器:a.push_back(x);
在末尾弹出容器:a.pop_back();
清空容器:a.clear();
查询元素个数:a.size();
首指针:a.begin();
插入元素在sit位置:a.insert(sit,x);其中sit是vector的迭代器。
其它像数组一样调用就可以了。
看做是一个动态数组

若没有元素时top会返回NULL

用两个栈表示一个队列的效果

把数压入a栈,然后依次弹出压入b栈,从b栈开始取。可达到队列效果。a为临时栈。

Queue

定义:queue a;
插入队尾:a.push(x);
删除队首:a.pop();
查询队尾:a.back();
查询队首:a.front();
查询长度:a.size();
清空只能慢慢pop。

搜索树

它可以用作字典,也可以用作优先队列。

如图所示,一颗二叉查找树是按二叉树结构来组织的。
这样的树可以用链表结构表示,其中每一个节点都是一个对象。
节点中包含 key, left, right 和 parent。
如果某个儿子节点或父节点不存在,则相应域中的值即为 NULL。

设 x 为二叉查找树中的一个节点。
如果 y 是 x 的左子树的一个节点,则 key[y] <= key[x].
如果 y 是 x 的右子树的一个节点,则 key[x] <= key[y].

二叉查找树的中序遍历

inorder(int x)
{
    if x != NULL{
        inorder(left[x]);
		printf(%d,key[x]);
		inorder(right[x]);
    }
}
若随机的数构造时一般使用二叉搜索树

如果各元素按照随机的顺序插入,则构造出的二叉查找树的期望高度为 O(log n)。

二叉查找树的查询

与二叉查找树的遍历相同原理,代码:

tree_search(int x,int k)
{
    if(x==NULL||k==key[x])
        return x;
   	if(k<key[x])
        return tree_search(left[x],k);
    else
        return tree_search(right[x],k);
}

二叉查找树查找最小/最大元素

最小元素

tree-Minimum(x)
{
    while(left[x]!=NULL)
        x=left[x];
    return x;
}

最大元素

tree-Minimum(int x)
{
    while(right[x]!=NULL)
        x=right[x];
    return x;
}

二叉查找树的前驱和后继

tree-successor(int x)
{
    if(right[x]!=NULL)
        return tree-Minimum(right[x])
    y=parent[x];
    while(y!=NUll&&x==right[y])
    {
        x=y;
        y=parent[y];
    }
    return y;
}

while(y!=NUll&&x==right[y])
{
x=y;
y=parent[y];
}

此段代码:若当前要求后继的点没有右子节点,需要找到当他不作为右子节点的父节点的值即为所求。

如图:

二叉查找树的插入

Tree-Insert(int t,int z)
{
    int y=NULL,x=root[t];
    while(x!=NULL)
    {
        y=x;
        if(key[z]<key[x]) 
            x=left[x];
        else
            x=right[x];
    }
    parent[z]=y;
    if(y==NULL)
        root[t]=z;
    else if(key[z]<key[y])
        left[y]=z;
    else right[y]=z;
}

二叉查找树的删除

四种情况

堆(Heap)

堆的性质

  • 完全二叉树的层次序列,可以用数组表示。
  • 堆中储存的数是局部有序的,堆不唯一。
  • 节点的值与其孩子的值之间存在限制。
  • 任何一个节点与其兄弟之间都没有直接的限制。
  • 从逻辑角度看,堆实际上是一种树形结构。

最小根堆和二叉查找树的区别

对于二叉搜索树的任意一个节点:

  • 左子树的值都小于这个节点的值
  • 右子树的值都大于这个节点的值
  • 两个子树都是二叉搜索树
  • 对于最小堆的任意一个节点:
  • 所有的子节点值都大于这个节点的值
  • 两个子树都是最小堆

eg1

给出两个长度为 n 的有序表 A 和 B,在 A 和 B 中各任取一个元素,可以得到 n2 个和,求这些和中最小的 n 个。
n <= 400000

分析:一共有n^2个和:
A[1] + B[1], A[1] + B[2], …, A[1] + B[n]
A[2] + B[1], A[2] + B[2], …, A[2] + B[n]

A[n] + B[1], A[n] + B[2], …, A[n] + B[n]

固定 A[i], 每 n 个和都是有序的:
A[1] + B[1] <= A[1] + B[2] <= … <= A[1] + B[n]
A[2] + B[1] <= A[2] + B[2] <= … <= A[2] + B[n]

A[n] + B[1] <= A[n] + B[2] <= … <= A[n] + B[n]

a[1]b[1]是最小的,第二小的可能是a[1]b[2],a[2]b[1]。就每次把这一个点右边和下边的数字压进去,找到最小的然后弹出。
每一次找一列 每一次找挑出最大的 一定是第几次的最小和:所以先把第一列放入根堆,找到最小然后弹出后,找到这一个点右边的点压入根堆,继续进行比较,重复上述过程。

暴力代码

int main()
{
	int n=read();
	for(int i=1;i<=n;++i) a[i]=read();
	for(int i=1;i<=n;++i) b[i]=read();
	for(int i=1;i<=n;++i)
	{
		for(int j=1;j<=n;++j)
		{
			int x=a[j]+b[i];
			q.push(x);
		}
		int x=q.top();
		q.pop();
		cout<<x<<" ";
	}
	return 0;
} 

优化版代码:
用结构体给,需要重载运算符。

最好可以用pair。

int main()
{
	int n=read();
	for(int i=1;i<=n;++i) a[i]=read();
	for(int i=1;i<=n;++i) b[i]=read();
	for(int i=1;i<=n;++i)
	{
		if(!q.empty())
		{
			Node v=q.top();
			u.sum=a[v.y+1]+b[v.x];
			u.x=v.y+1;u.x=j;
		}
		u.sum=a[j]+b[i];
		u.x=i;u.y=j;
		q.push(u);
	}
	return 0;
}
bool operator < (const node& a, const node&b) {
    return a.x<b.x;
}

eg2

丑数是指质因子在集合 {2, 3, 5, 7} 内的整数,第一个丑数是 1.现在输入 n,输出第 n 大的丑数。
n <= 10000.
#include<iostream>
#include<cstdio>
#include<queue>
#include<vector>
#include<cmath>
#include<stack>
using namespace std;
int n,cnt=0;
priority_queue<<int> vector<int>, greater<int> > q;
inline int read()
{
	int s=0,w=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){s=s*19+ch-'0';ch=getchar();}
	return s*w;
}
inline void ugly(int n,int a,int b,int c,int d,int x,int cnt)
{
	if(cnt==n)	return;

	q.push(pow(2,a+1)*pow(3,b)*pow(5,c)*pow(7,d))
	if(pow(2,a+1)*pow(3,b)*pow(5,c)*pow(7,d)==x) q.pop();
	return ugly(a+1,b,c,d,pow(2,a+1)*pow(3,b)*pow(5,c)*pow(7,d),cnt+1); 
	
	q.push(pow(2,a)*pow(3,b+1)*pow(5,c)*pow(7,d))
	if(pow(2,a)*pow(3,b+1)*pow(5,c)*pow(7,d)==x) q.pop();
	return ugly(a,b+1,c,d,pow(2,a)*pow(3,b+1)*pow(5,c)*pow(7,d),cnt+1); 
	
	q.push(pow(2,a)*pow(3,b)*pow(5,c+1)*pow(7,d))
	if(pow(2,a)*pow(3,b)*pow(5,c+1)*pow(7,d)==x) q.pop();
	return ugly(a,b,c+1,d,pow(2,a)*pow(3,b)*pow(5,c+1)*pow(7,d),cnt+1); 
	
	q.push(pow(2,a)*pow(3,b)*pow(5,c)*pow(7,d+1))
	if(pow(2,a)*pow(3,b)*pow(5,c)*pow(7,d+1)==x) q.pop();
	return ugly(a,b,c,d+1,pow(2,a)*pow(3,b)*pow(5,c)*pow(7,d+1),cnt+1); 
}
int main()
{
	int a,b,c,d,x;
	n=read();
	a=b=c=d=0;
	x=pow(2,a)*pow(3,b)*pow(5,c)*pow(7,d);
	q.push(x);
	ugly(a,b,c,d,x);
	for(int i=1;i<=n;++i)
	{
		int y=q.top();q.pop();
		if(i==n)
			cout<<y<<endl;
	}
	return 0;
}

线段树

线段树1:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>

#define ull unsigned long long

const int Maxn=100001;

ull n,m,a[Maxn],ans[Maxn<<2],tag[Maxn<<2];

inline ull read()
{
	int s=0,w=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){s=s*10+ch-'0';ch=getchar();}
	return s*w;
}

inline ull ls(ull x)
{
	return x<<1;
}

inline ull rs(ull x)
{
	return x<<1|1;
}

void scan()
{
	n=read();m=read();
	for(int i=1;i<=n;++i)
		a[i]=read();
}

inline void pushup(ull p)
{
	ans[p]=ans[ls(p)]+ans[rs(p)];
}

void build(ull p,ull l,ull r)
{
	tag[p]=0;
	if(l==r){ans[p]=a[l];return;}
	ull mid=(l+r)>>1;
	build(ls(p),l,mid);
	build(rs(p),mid+1,r);
	pushup(p);
}

inline void f(ull p,ull l,ull r,ull k)
{
	tag[p]=tag[p]+k;
	ans[p]=ans[p]+k*(r-l+1);
}

inline void pushdown(ull p,ull l,ull r)
{
	ull mid=(l+r)>>1;
	f(ls(p),l,mid,tag[p]);
	f(rs(p),mid+1,r,tag[p]);
	tag[p]=0;
}

inline void update(ull nl,ull nr,ull l,ull r,ull p,ull k)
{
	if(nl<=l&&r<=nr)
	{
		ans[p]+=k*(r-l+1);
		tag[p]+=k;
		return;
	}
	pushdown(p,l,r);
	ull mid=(l+r)>>1;
	if(nl<=mid) update(nl,nr,l,mid,ls(p),k);
	if(nr>mid) update(nl,nr,mid+1,r,rs(p),k);
	pushup(p);
}

ull query(ull qx,ull qy,ull l,ull r,ull p)
{
	ull res=0;
	if(qx<=l&&r<=qy) return ans[p];
	ull mid=(l+r)>>1;
	pushdown(p,l,r);
	if(qx<=mid) res+=query(qx,qy,l,mid,ls(p));
	if(qy>mid) res+=query(qx,qy,mid+1,r,rs(p));
	return res;
}

int main()
{
	ull a1,b,c,d,e,f;
	scan();
	build(1,1,n);
	while(m--)
	{
		a1=read();
		switch(a1)
		{
			case 1:{
				b=read();c=read();d=read();
				update(b,c,1,n,1,d);
				break;
			}
			case 2:{
				e=read();f=read();
				printf("%lld\n",query(e,f,1,n,1));
				break;
			}
		}
	}
	return 0;
}
线段树2:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;

typedef unsigned long long ull;

const int Maxn=200002;

ull n,m,MOD,a[Maxn];

struct Node{
	ull sum,multag,addtag;
}tr[Maxn<<2];

inline ull read()
{
	ull w=1,s=0;
	char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){s=s*10+ch-'0';ch=getchar();}
	return s*w;
}
inline ull ls(ull x){return x<<1;}
inline ull rs(ull x){return x<<1|1;}
inline void pushup(ull p)
{
	tr[p].sum=(tr[ls(p)].sum+tr[rs(p)].sum)%MOD;
	return;
}

inline void scan()
{
	n=read();m=read();MOD=read();
	for(register ull i=1;i<=n;++i)
		a[i]=read();
	return;
}

inline void build(ull l,ull r,ull p)
{
	tr[p].multag=1;
	tr[p].addtag=0;
	if(l==r){tr[p].sum=a[l];return;}
	ull mid=(l+r)>>1;
	build(l,mid,ls(p));
	build(mid+1,r,rs(p));
	pushup(p);
	return;
}

inline void pushdown(ull l,ull r,ull p)
{
	ull mid=(l+r)>>1;
	
	tr[ls(p)].sum=(tr[ls(p)].sum*tr[p].multag%MOD+tr[p].addtag*(mid-l+1)%MOD)%MOD;
	tr[rs(p)].sum=(tr[rs(p)].sum*tr[p].multag%MOD+tr[p].addtag*(r-mid)%MOD)%MOD;
	
	tr[ls(p)].addtag=(tr[ls(p)].addtag*tr[p].multag%MOD+tr[p].addtag)%MOD;
	tr[rs(p)].addtag=(tr[rs(p)].addtag*tr[p].multag%MOD+tr[p].addtag)%MOD;
	
	tr[ls(p)].multag=(tr[p].multag*tr[ls(p)].multag)%MOD;
	tr[rs(p)].multag=(tr[p].multag*tr[rs(p)].multag)%MOD;
	
	tr[p].multag=1;
	tr[p].addtag=0;
	
	return;
}

inline void upd1(ull nl,ull nr,ull l,ull r,ull p,ull k)
{
	if(nl<=l&&r<=nr)
	{
		tr[p].addtag=(tr[p].addtag+k)%MOD;
		tr[p].sum=(tr[p].sum+k*(r-l+1)%MOD)%MOD;
		return;
	}
	pushdown(l,r,p);
	ull mid=(l+r)>>1;
	if(nl<=mid) upd1(nl,nr,l,mid,ls(p),k);
	if(nr>mid) upd1(nl,nr,mid+1,r,rs(p),k);
	pushup(p);
	return;
}

inline void upd2(ull nl,ull nr,ull l,ull r,ull p,ull k)
{
	if(nl<=l&&r<=nr)
	{
		tr[p].addtag=tr[p].addtag*k%MOD;
		tr[p].multag=tr[p].multag*k%MOD;
		tr[p].sum=tr[p].sum*k%MOD;
		return;
	}
	ull mid=(l+r)>>1;
	pushdown(l,r,p);
	if(nl<=mid) upd2(nl,nr,l,mid,ls(p),k);
	if(nr>mid) upd2(nl,nr,mid+1,r,rs(p),k);
	pushup(p);
	return;
}

inline ull query(ull ql,ull qr,ull l,ull r,ull p)
{
	ull res=0;
	if(ql<=l&&r<=qr){return tr[p].sum;}
	ull mid=(l+r)>>1;
	pushdown(l,r,p);
	if(ql<=mid) res+=query(ql,qr,l,mid,ls(p));
	if(qr>mid) res+=query(ql,qr,mid+1,r,rs(p));
	return res%MOD;
}

inline void print(int l,int r,int p)
{
	if(l==r) {printf("%lld ",tr[p].sum);return;}
	int mid=(l+r)>>1;
	print(l,mid,ls(p));
	print(mid+1,r,rs(p));
	return;
}

int main()
{
	scan();
	build(1,n,1);
	while(m--)
	{
		ull o,x,y,z;
		o=read();
		switch(o)
		{
			case 1:{
				x=read();y=read();z=read();
				upd2(x,y,1,n,1,z);
				break;
			}
			case 2:{
				x=read();y=read();z=read();
				upd1(x,y,1,n,1,z);
				break;
			}
			case 3:{
				x=read();y=read();
				printf("%lld\n",query(x,y,1,n,1));
				break;
			}
			
		}
	
	}
	return 0;
}
问题1:有一个长度为 n 的序列,a[1], a[2], …, a[n]。现在执行 m 次操作,每次可以执行以下两种操作之一:
1. 将下标在区间 [l, r] 的数都加上 v。
2. 询问一个下标区间 [l, r] 中所有数的最小值的个数。
修改部分:在pushup里注意两点:

1.如果两个子点的最小值相等,父节点的最小值个数就是两个儿子最小值个数相加。

2.如果两个子点的最小值不相等,比较两者最小值,更新父节点的最小值。

if(Min[ls(x)]==Min[rs(x)])
{
    cnt[x]=cnt[ls(x)]+cnt[rs(x)];
    Min[x]=Min[ls(x)];
}
else
{
    if(Min[ls(x)]>Min[rs(x)])
    {
        Min[x]=Min[rs(x)];
        cnt[x]=cnt[rs(x)];
    }
    else
    {
        Min[x]=Min[ls(x)];
        cnt[x]=cnt[ls(x)];
    }
}
问题2:你有一个长度为 n 的序列 A[1], A[2], …, A[N].
询问:Query(x, y) = max { A[i] + … + A[j]; x <= i <= j <= y}给出 M 组 (x, y),请给出 M 次询问的答案。
	|A[i]| <= 15007, 1 <= N,M <= 50000
出处:SPOJ GSS1 Can you answer these queries I

分析:考虑用线段树来维护每个区间的答案。需要考虑如何传递合并子节点的结果:

关键引入:后缀和与前缀和一同使用

设x结点的答案为smax[x],区间前缀和最大值为lmax[x],后缀最大和rmax[x];

此时有两种情况:

  1. 所求区间x到y完整覆盖了x点的区间:smax[x]=max(smax[ls],smax[rs]);

  2. 所求区间x到y未完全覆盖x点的区间:smax[x]=rmax[ls]+lmax[rs];

    故最终smax[x]=max(max(smax[ls],smax[rs]),rmax[ls]+lmax[rs]);

怎样更新 lmax和rmax?

​ 1.lmax[x]=max(lmax[ls],sum[ls]+lmax[rs]);

​ 2.rmax[x]=max(rmax[rs],sum[rs]+rmax[ls]);

建树时上述的值都相等 故有:

if (l == r){
        smax[x] = lmax[x] = rmax[x] = sum[x] = a[l];
        return;
    }

你有一个长度为 n 的序列 A[1], A[2], …, A[N].
询问:
Query(x1, y1, x2, y2) = max { A[i] + … + A[j]; x1 <= i <= y1, x2 <= j <= y2}
x1 <= x2, y1 <= y2
给出 M 组操作,输出每次询问的答案

|A[i]| <= 10000, 1 <= N,M <= 10000

出处:SPOJ GSS5 Can you answer these queries V

分析:解题关键——是否存在交集。

如果 [x1, y1] 和 [x2, y2] 没有交集,即 y1 < x2
答案显然等于: Rmax([x1, y1]) + Sum(y1 + 1, x2 - 1) + Lmax([x2, y2])

如果 [x1, y1] 和 [x2, y2] 有交集,即 y1 >= x2
这个时候,区间分为三个部分:
[x1, x2 - 1], [x2, y1], [y1 + 1 .. y2]
左端点有两种选择,右端点也有两种选择,一共四种情况。
进一步讨论,变为三种情况:
Smax([x2, y1])
Rmax([x1, x2 - 1]) + Lmax([x2, y2])
Rmax([x1, y1]) + Lmax([y1 + 1 .. y2])

DFS序

可以将一维形式转化为二维形式。通过遍历一个点的进出顺序写成区间的形式。

一定可以构造一个线段树。

LCA

定义:给出有根树T中的两个不同的节点u和v,找到一个离根最远的节点x,使得x同时是u和v的祖先,x就是u和v的最近公共祖先(Lowest common ancestor)
例子:
B和M的最近公共祖先是A
K和J的最近公共祖先是D
C和H的最近公共祖先是C

注:可以把自己当作祖先

倍增算法

我们记fa[u]为u的父节点,即从u向根走1步能到达的节点,对根节点我们记fa[root] = 0.
记up [u] [k] 为从u向根走2k步到达的节点。
显然有
up [u] [0] = fa[u]
up [u] [k] = up [ up [u] [k – 1] ] [k – 1] 递推关系式
例如
up [“K”] [0] = “I”,
up [”I”] [0] = ”D”,
up [”K”] [1] = “D”.

深度:到根据节点的距离,可以自定标准。

具体实现

第一步:把节点的深度化为相同,让深度更深的节点用倍增算法向上走;特殊情况:若一个点是另一个点的祖先,将会重合于同一祖先点,则可以停止寻找。

第二步(深度相同且不在同一节点):让u和v同时向上跳跃,每次跳

\[2^n \]

注:祖先的祖先还是祖先,故不可以从大到小枚举。
###### 			任何十进制数都可以转换成2的n次幂的和

复杂度:O(log(n))

代码实现

#include<bits/stdc++.h>
using namespace std;
const int MAXN=500001;
struct node{
	int nxt,to;
}e[MAXN<<1];
int h[MAXN];
int n,m,s,cnt;
void add(int x,int y)
{
	++cnt;
	e[cnt].to=y;
	e[cnt].nxt=h[x];
	h[x]=cnt;
}
int D[MAXN],f[MAXN][21];
void dfs(int r,int fa)
{
	D[r]=D[fa]+1;
	f[r][0]=fa;
	for(int i=1;(1<<i)<=D[r];i++)
	f[r][i]=f[f[r][i-1]][i-1];
	for(int i=h[r];i;i=e[i].nxt)
	{
		int v=e[i].to;
		if(v!=fa) dfs(v,r);
	}
}
int lca(int x,int y)
{
	if(D[x]<D[y]) swap(x,y);
	for(int i=20;i>=0;i--)
	{
		if(D[y]<=D[x]-(1<<i))
		x=f[x][i];
	}
	if(x==y) return y;
	for(int i=20;i>=0;i--)
	{
		if(f[x][i]!=f[y][i])
		{
			x=f[x][i];
			y=f[y][i];
		}
	}
	return f[x][0];
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cin>>n>>m>>s;
	for(int i=1;i<n;i++)
	{
		int x,y;
		cin>>x>>y;
		add(x,y);
		add(y,x);
	}
	dfs(s,0);
	for(int i=1;i<=m;i++)
	{
		int a,b;
		cin>>a>>b;
		cout<<lca(a,b)<<endl;
	}
	return 0;
}

树状数组

前缀和

有一个长度为 n 的序列 A[1], A[2], …, A[n]
每次给出一个询问 (x, y):
请你求出 A[x .. y] 中出现过的数字之和。(出现多次的数只计算一次)
n <= 30000, Q <= 100000

思路:

1.无法使用线段树,因为回溯点的时候无法合并。

2.可以使用桶排暴力的方式。

3.还有一种思路,记录一个数组 left[i],表示左边第一个出现的相同数字 a[i] 的下标。
这样如果 left[i] < x,就说明 a[i] 是 [x, y] 中第一个出现的 a[i].
如果 left[i] >=x,就说明在 [x, i - 1] 中已经出现过一次 a[i]了,不用累计进答案。

处理left数组的时候需要用到离散化或map。

二维前缀和

sum [i] [j]=sum [i-1] [j]+sum [i] [j-1]-sum [i-1] [j-1]+a [i] [j] 递推公式

例:以(x1,y1)为左上角 (x2,y2)为右下角的前缀和:

S(x1,y1,x2,y2)=sum[x2] [y2]-sum[x1] [y2]-sum[x2] [y1]+sum[x1-1] [y2-1]; 注意边界

树状数组只能用于单点修改

树状数组的思想:

元素个数的二进制就是下标的二进制表示中最低位的1 所在的位置对应的数。

Lowbit(x):

\[C(x) = x and -x \]

树状数组的查询:
ans = 0
while (i > 0) ans += sum[i], i -= C(i);
树状数组修改:与查询不同的是,这里是每一步循环给下标加上 C(i)
change(i, v):
		while(i <= n)	sum[i] += v, i += C(i)

例1:二维平面上有 n 个点 (x[i], y[i])。现在请你求出每个点左下角的点的个数。
n <= 15000, 0 <= x, y <= 32000

分析—把二维数据结构转换为一维数据结构

注意到在 i 左下角的点也就是满足 x <= x[i], y <= y[i] 的点。
还是运用扫描线的思想,把一维限制直接去掉。
从小到大枚举 y[i],每次都把 y <= y[i] 的点插入一个集合 S。

y已经满足了,但x不一定满足。

故在 S 里查询 x <= x[i] 的个数即可。

例2:给定一个数列 A[1], A[2], …, A[n].
问有多少个有序对 (x, y),满足:
x < y
A[x] >= y
A[y] >= x
n <= 200000

分析:看上去要满足三个条件,实际上我们可以合并前两个得到:
统计 x < y <= A[x] 且 A[y] >= x 的数对。
通过对一维排序,按顺序处理,来满足这维要求。
然后统计第二维的信息即可。

枚举x,从大到小维护y

若a[y]>=x,标记此y点

最后只需要查询从x到a[x]区间内有多少标记即可。

例3:二维平面上有 n 个点 (x[i], y[i])。
现在请你求出每个点左下角的点的个数。
n <= 15000, 0 <= x, y <= 32000

​ 注意到在 i 左下角的点也就是满足 x <= x[i], y <= y[i] 的点。
还是运用扫描线的思想,把一维限制直接去掉。
从小到大枚举 y[i],每次都把 y <= y[i] 的点插入一个集合 S。
然后在 S 里查询 x <= x[i] 的个数即可。

​ 具体来说,我们从小到大枚举 y 坐标。
每次把 y 坐标小于等于当前枚举值的点的横坐标 x 插入 S 集合。
对于一个点 (x[i], y[i]) (y[i]等于当前枚举的y坐标),它左下角的点的个数等于 S 中小于等于 x[i] 的个数。

​ 只需要用树状数组来维护 S 集合中的数。
插入一个 x,就把 add(x, 1)
查询的时候,把 query(x[i]) 加入答案即可。
时间复杂度是 O(n log n)。

Map

映射,把它看做一个无限大的数组。
定义方式:map<int ,int> a;
使用方式:a[x]++,cout<<a[y]等。
利用迭代器查询map里的所有二元组:
for (map<int,int>::iterator sit=a.begin(); sit!=a.end(); sit++) cout<first<<‘ ‘<second<<endl;
清空:a.clear();

Hash

解决哈希冲突的办法:
1.挂链组。

2.将重复的y向上移,若想查找只需要找原来位置是否为y,向上找即可。

3.第一次重复向上移一位,后移两位,四位等。避免冲突频率。

字符串哈希

用数字代表字符串中的字母,比较两者之间的哈希值即可比较两字符串是否相同。

好处:可以同时比较多个字符串。

KMP

有了字串的hash值,在字符串中找长度为m的子字符串的个数。

Set

定义:set a;

实现方式:红黑树。

插入元素:a.insert(100);
清空set:a.clear();
查询set中有多少元素:a.size();
查询首元素:a.begin(),返回iterator。
查询最后一个元素+1:a.end(),返回iterator。
删除:a.erase(sit); sit是一个iterator类型的指针或者是元素值。
判断是否有数字100:a.count(100),返回bool值。用二叉树的遍历就可以实现。

从小到大遍历set:
for (set::iterator sit=a.begin(); sit!=a.end(); sit++) cout<<*sit<<endl;

注:加粗部分的复杂度为O(logn),相当于去找下一个子节点。
例:宠物领养场

凡凡开了一间宠物收养场。收养场提供两种服务:收养被主人遗弃的宠物和让新的主人领养这些宠物。

每个领养者都希望领养到自己满意的宠物,凡凡根据领养者的要求通过他自己发明的一个特殊的公式,得出该领养者希望领养的宠物的特点值a(a是一个正整数,a<2^31),而他也给每个处在收养场的宠物一个特点值。这样他就能够很方便的处理整个领养宠物的过程了,宠物收养场总是会有两种情况发生:被遗弃的宠物过多或者是想要收养宠物的人太多,而宠物太少。

被遗弃的宠物过多时,假若到来一个领养者,这个领养者希望领养的宠物的特点值为a,那么它将会领养一只目前未被领养的宠物中特点值最接近a的一只宠物。(任何两只宠物的特点值都不可能是相同的,任何两个领养者的希望领养宠物的特点值也不可能是一样的)如果有两只满足要求的宠物,即存在两只宠物他们的特点值分别为a-b和a+b,那么领养者将会领养特点值为a-b的那只宠物。

收养宠物的人过多,假若到来一只被收养的宠物,那么哪个领养者能够领养它呢?能够领养它的领养者,是那个希望被领养宠物的特点值最接近该宠物特点值的领养者,如果该宠物的特点值为a,存在两个领养者他们希望领养宠物的特点值分别为a-b和a+b,那么特点值为a-b的那个领养者将成功领养该宠物。

一个领养者领养了一个特点值为a的宠物,而它本身希望领养的宠物的特点值为b,那么这个领养者的不满意程度为abs(a-b)。

你得到了一年当中,领养者和被收养宠物到来收养所的情况,请你计算所有收养了宠物的领养者的不满意程度的总和。这一年初始时,收养所里面既没有宠物,也没有领养者

分析:用lowbound找到一个相近x的元素指针,指针--去找另外一个相近的宠物。

比较x与第一个指针所代表宠物的值的差和第二个指针所代表宠物值的差。

最后删除此元素代表宠物离开宠物店。

s.erase()内可以写指针也可以写值。

常用手写平衡树:Treep,Splay

树状数组求逆序对

一串数字得到后标上序号。

从序号为1的开始讨论,出现一个点就标记到相应的数组位置上。

再往下讨论的时候,看该数右面有多少点被标记了,每有一个被标记就加进逆序对的个数。

posted @ 2020-01-18 22:45  MrMorrins  阅读(159)  评论(0编辑  收藏  举报