• 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
    ",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
    ",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的开始讨论,出现一个点就标记到相应的数组位置上。

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

  • 相关阅读:
    CentOS yum 源的配置与使用
    在css当中使用opacity
    CSS position属性absolute relative等五个值的解释
    uni APP 微信小程序获取授权的微信信息
    vue-admin-element 打包发布后IE报错的问题
    RMAN恢复数据文件
    怎么删除表空间对应的某一个数据文件
    Oracle存储过程、函数、包加密wrap
    Oracle加密解密
    Interval 用法总结
  • 原文地址:https://www.cnblogs.com/Morrins/p/12210583.html
Copyright © 2020-2023  润新知