浅谈数据结构题的几个非经典解法【有感】

主要内容:

     二进制分组、整体二分、对时间分组等;

1.整体二分:

      首先觉得这个和cdq分治很像,但还是有一些区别的,例如:

        cdq分治是将[l,mid]的操作来更新[mid+1,r]

        整体二分是把询问分为[l,mid]和[mid+1,r]两部分

      实际上上面就已经道出了整体二分的本质思想,就是划而问之,还是从几道例题来讲讲整体二分比较好

      T1:Pku 2104 K-th Number[http://begin.lydsy.com/JudgeOnline/problem.php?id=2971]

        题目大意:给定一个数组,求区间第K大

        题解:看到这个,很多人会说这不就是可持久化线段树的模版题吗?

           但是,这道题可以用整体二分轻松A,并且代码短,速度快!!!

           1.我们先把数组拆分为一个个插入操作和询问操作一起放入一个数组中

           2.solve(L,R,l,r) 代表(询问及操作在l--r的范围內)

            我们先二分一个mid=(l+r)>>1;

            然后将插入操作Pi小于等于mid的加入树状数组,并且将其加入p1数组中,若操作pi加入p2数组中!

            然后将每个询问算一下为tmp,如果tmp>x将其加入p2数组,否则加入p1数组!

            然后清零树状数组,solve(L,L+t1-1,l,mid) solve(L+t1,R,mid+1,r)即可

            如果l==r统计答案就ok了!

           3.到这里你应该已经体会到整体二分的优秀性:代码短而不失高效率,正如树状数组般优美!

      T2:矩阵乘法[http://www.lydsy.com/JudgeOnline/problem.php?id=2738]

         题目大意:给你一个N*N的矩阵,不用算矩阵乘法,但是每次询问一个子矩形的第K小数。

         题解:T1的二维拓展,开个二维树状数组即可!

      T3:zjoi2013 K大树查询

        题目大意:有N个位置,M个操作。操作有两种,每次操作如果是1 a b c的形式表示在第a个位置到第b个位置,每个位置加入一个数c
             如果是2 a b c形式,表示询问从第a个位置到第b个位置,第C大的数是多少。

        题解:算法一:区间线段树+权值线段树  

           算法二:整体二分+区间修改/询问树状数组

               整体二分部分正如上文,但是本题重点在于区间修改/询问树状数组

               1.单点修改+单点询问

                  普通树状数组即可

               2.区间修改+单点询问

                  考虑差分即可

                  我们兴建一个查分数组来做树状数组,并且将原有a数组保留  

                  修改时就修改delta数组即可

                  询问就加上delta的前缀和与a数组即可

               3.区间修改+区间询问

                  首先依旧是引入delta数组 delta[i]表示区间 [i, n] 的共同增量

                  于是修改区间 [l, r] 时修改 delta[l] 和 delta[r + 1] 即可(就是差分的思路)

                  查询的时候是查询区间 [l, r] 的和 即sum[r] - sum[l - 1] 所以现在的问题是求sum[i]

                  sum[i]=a[1]+....+a[i]+delta[1]*i+delta[2]*(i-1)...delta[i]*1

                      =sigma(a[x])+sigma(delta[x]*(i+1-x))

                      =sigma(a[x])+(i+1)*sigma(delta[x])-sigma(delta[x]*x)

                  于是维护两个树状数组delta[x]和delta[x]*x即可!

代码:

T1

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<cstdio>
#define N  150005
using namespace std;
struct point{
    int x,id,l,r,pos,op;
}p[N],p1[N],p2[N];
int n,q,mx,mn,tot,ans[N],c[N];
int read()
{
    int x=0,f=1; char ch;
    while (ch=getchar(),ch<‘0‘||ch>‘9‘) if (ch==‘-‘) f=-1;
    while (x=x*10+ch-‘0‘,ch=getchar(),ch>=‘0‘&&ch<=‘9‘);
    return x*f;
}
void add(int x,int val){for (int i=x; i<=n; i+=i&-i) c[i]+=val;
}
int query(int x){
    int res=0; for (int i=x; i; i-=i&-i) res+=c[i]; return res;
}
void solve(int l,int r,int x,int y){
    if (x==y){for (int i=l; i<=r; i++) if (!p[i].op) ans[p[i].id]=x; return ;
    }
    int mid=(x+y)>>1; int t1=0,t2=0;
    for (int i=l; i<=r; i++){
        if (p[i].op==1){
            if (p[i].x<=mid) add(p[i].pos,1),p1[++t1]=p[i]; else p2[++t2]=p[i];
        }
        else{
            int tt=query(p[i].r)-query(p[i].l-1);
            if (p[i].x<=tt) p1[++t1]=p[i];
            else p[i].x-=tt,p2[++t2]=p[i];
        }
    }
    for (int i=1; i<=t1; i++) if (p1[i].x<=mid && p1[i].op==1) add(p1[i].pos,-1);
    for (int i=1; i<=t1; i++) p[i+l-1]=p1[i];
    for (int i=1; i<=t2; i++) p[l+t1+i-1]=p2[i];
    solve(l,l+t1-1,x,mid); solve(l+t1,r,mid+1,y);
}
int main()
{
    n=read(); q=read(); mn=0x7fffffff,mx=0;
    for (int i=1; i<=n; i++){int x=read(); p[++tot].x=x; p[tot].op=1; p[tot].pos=i;  mn=min(mn,x); mx=max(mx,x);
    }
    for (int i=1; i<=q; i++){p[++tot].l=read(); p[tot].r=read(); p[tot].x=read(); p[tot].op=0; p[tot].id=i;
    }
    solve(1,tot,mn,mx);
    for (int i=1; i<=q; i++) cout<<ans[i]<<endl;
    return 0;
}

T2

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<cstdio>
#define N 505
#define M 310005
using namespace std;
struct point{int x1,y1,x2,y2,op,pos,k;
}p[M],p1[M],p2[M];
int c[N][N],ans[M];
int n,q,mn,mx,tot;
int read()
{
    int x=0,f=1; char ch;
    while (ch=getchar(),ch<‘0‘||ch>‘9‘) if (ch==‘-‘) f=-1;
    while (x=x*10+ch-‘0‘,ch=getchar(),ch>=‘0‘&&ch<=‘9‘);
    return x*f;
}
void add(int x,int y,int val){
    for (int i=x; i<=n; i+=i&-i)
        for (int j=y; j<=n; j+=j&-j) c[i][j]+=val;
}
int query(int x,int y){
    int res=0;
    for (int i=x; i; i-=i&-i)
        for (int j=y; j; j-=j&-j) res+=c[i][j];
    return res;
}
void solve(int L,int R,int l,int r){
    if (L>R) return;
    if (l==r){for (int i=L; i<=R; i++) if (p[i].op) ans[p[i].pos]=l; return;
    }
    int mid=(l+r)>>1,t1=0,t2=0;
    for (int i=L; i<=R; i++)
    if (p[i].op==0){
        if (p[i].k<=mid) add(p[i].x1,p[i].y1,1),p1[++t1]=p[i]; else p2[++t2]=p[i];
    }
    else{
        int tmp=query(p[i].x2,p[i].y2)-query(p[i].x2,p[i].y1-1)-query(p[i].x1-1,p[i].y2)+query(p[i].x1-1,p[i].y1-1);
        if (p[i].k<=tmp) p1[++t1]=p[i]; else p[i].k-=tmp,p2[++t2]=p[i];
    }
    for (int i=1; i<=t1; i++) if (!p1[i].op) add(p1[i].x1,p1[i].y1,-1);
    for (int i=1; i<=t1; i++) p[L+i-1]=p1[i];
    for (int i=1; i<=t2; i++) p[L+t1-1+i]=p2[i];
    solve(L,L+t1-1,l,mid); solve(L+t1,R,mid+1,r);
}
int main()
{
    n=read(); q=read();
    mn=1e9,mx=-mn;
    for (int i=1; i<=n; i++)
        for (int j=1; j<=n; j++) p[++tot].op=0,p[tot].x1=i,p[tot].y1=j,p[tot].k=read(),mx=max(mx,p[tot].k),mn=min(mn,p[tot].k);
    for (int i=1; i<=q; i++){
        p[++tot].x1=read(),p[tot].y1=read(),p[tot].x2=read(),p[tot].y2=read(),p[tot].k=read(),p[tot].pos=i,p[tot].op=1;
    }
    solve(1,tot,mn,mx);
    for (int i=1; i<=q; i++) printf("%d\n",ans[i]);
    return 0;
}

T3

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<cmath>
#define N 500005
#define ll long long
using namespace std;
struct point{
    int op,a,b,pos;
    ll c;
}p[N],p1[N],p2[N];
int ans[N];
ll c[N],cc[N];
bool bo[N];
int n,m;
ll read(){
    ll x=0,f=1; char ch;
    while (ch=getchar(),ch<‘0‘||ch>‘9‘) if (ch==‘-‘) f=-1;
    while (x=x*10+ch-‘0‘,ch=getchar(),ch>=‘0‘&&ch<=‘9‘);
    return x*f;
}
void add(ll *a,int pos,int val){
    for (int i=pos; i<=n;i+=i&-i){a[i]+=val;
    }
}
ll get(ll *a,int pos){
    ll res=0;
    for (int i=pos; i; i-=i&-i){res+=a[i];
    }return res;
}
ll query(int l,int r){
    ll suml=0,sumr=0;
    suml=1ll*l*get(c,l-1)-get(cc,l-1);
    sumr=1ll*(r+1)*get(c,r)-get(cc,r);
    return sumr-suml;
}
void updata(int l,int r,int x){
    add(c,l,x); add(c,r+1,1ll*-x);
    add(cc,l,1ll*x*l); add(cc,r+1,1ll*-x*(r+1));
}
void solve(int L,int R,int l,int r){
    if (R<L) return;
    if (l==r){
        for (int i=L; i<=R; i++) if (p[i].op==2) ans[p[i].pos]=l;
        return;
    }
    int mid=(l+r)>>1;
    int t1=0,t2=0;
    for (int i=L; i<=R; i++){
        if (p[i].op==1){
            if (p[i].c<=mid) p1[++t1]=p[i];
            else{
                p2[++t2]=p[i]; updata(p[i].a,p[i].b,1);
            }
        }
        else{
            ll tmp=query(p[i].a,p[i].b); if (tmp<p[i].c) p[i].c-=tmp,p1[++t1]=p[i]; else p2[++t2]=p[i];
        }
    }
    for (int i=L; i<=R; i++) if (p[i].op==1 && p[i].c>mid) updata(p[i].a,p[i].b,-1);
    for (int i=1; i<=t1; i++) p[L+i-1]=p1[i];
    for (int i=1; i<=t2; i++) p[L+t1-1+i]=p2[i];
    solve(L,L+t1-1,l,mid); solve(L+t1,R,mid+1,r);
}
int main(){
//    freopen("sequence.in","r",stdin);
//    freopen("sequence.out","w",stdout);
    n=read(); m=read();
    for (int i=1; i<=m; i++){
        p[i].op=read(),p[i].a=read(),p[i].b=read(),p[i].c=read(); p[i].pos=i;
        if (p[i].op==2) bo[i]=1;
    }
    solve(1,m,-n,n);
    for (int i=1; i<=m; i++) if (bo[i]) printf("%d\n",ans[i]);
    return 0;
}

  

 

时间: 2025-01-01 01:54:21

浅谈数据结构题的几个非经典解法【有感】的相关文章

浅谈数据结构-二叉树

浅谈数据结构-二叉树 二叉树是树的特殊一种,具有如下特点:1.每个结点最多有两颗子树,结点的度最大为2.2.左子树和右子树是有顺序的,次序不能颠倒.3.即使某结点只有一个子树,也要区分左右子树. 一.特殊的二叉树及特点 1.斜树 所有的结点都只有左子树(左斜树),或者只有右子树(右斜树).这就是斜树,应用较少 2.满二叉树 所有的分支结点都存在左子树和右子树,并且所有的叶子结点都在同一层上,这样就是满二叉树.就是完美圆满的意思,关键在于树的平衡. 根据满二叉树的定义,得到其特点为: 叶子只能出现

浅谈数据结构-树

树是一种数据结构,其中一个元素可以有两个或者多个数据元素,具有一对多的特点,用树结构来存储文件. 树的概念 结点的度:子结点的个数.例如结点1中有3个子结点,结点1的度是3. 树的度:树的度等于所有结点度中度最高的值.结点最高的度为3,树的度为3. 叶子结点:度为0的结点,即没有子结点的结点.例如:上图中3,5,6,7,9,10. 分支结点:除了叶子结点以外的结点,即度不为0的结点.例如:上面树的分支结点为1,2,4,8. 内部结点:除了根结点以及叶子结点或在分支结点的基础之上在去掉根结点.例如

浅谈数据结构之线性表顺序存储(一)

 首先,数据结构是由某一数据元素集合及该集合中所有数据元素之间的关系组成.具体来说,数据结构应当包含三方面的内容:(1).数据的逻辑结构:(2).数据的存储结构:(3).对数据所施加的操作.而数据的存储结构形式有两种:顺序存储与链式存储.在这里,先谈一谈线性表的顺序存储. 线性表:零个或多个数据元素的有限序列.第一,它是一个序列,也就是说,元素之间是有顺序的:第二,它是有限的,即元素个数是有限的.而线性表的顺序存储结构,说白了,就是在内存中找块地,通过占位的形式把一定的内存空间给占了,然后把相同

python学习-09(查找、排序和浅谈数据结构)

查找的方法: 排序的方法: 简单的数据结构: 一.算计基础 1.1.什么是算法: 算法(Algorithm)是指解题方案的准确而完整的描述,是一系列解决问题的清晰指令,算法代表着用系统的方法描述解决问题的策略机制.也就是说,能够对一定规范的输入,在有限时间内获得所要求的输出.如果一个算法有缺陷,或不适合于某个问题,执行这个算法将不会解决这个问题.不同的算法可能用不同的时间.空间或效率来完成同样的任务.一个算法的优劣可以用空间复杂度与时间复杂度来衡量. 简单的说,算法就是一个计算的过程,解决问题的

浅谈数据结构(一) 线性表 Lists

一.vector和list 线性结构中,比较重要的有 vector和list,这两个都是C++的标准模板库(C++ Standard Template Library)中的库文件. 访问操作,查找和删除 vector可以提供下标访问,即v[i]的方式,所以索引方便.然而如果要插入数据,尤其是在下标小的地方插入,需要把其后面所有的数据全部都往后移动一位,因此代价非常高.同样的,删除数据也是这样. list在C++中指的是双向链表,由于是指针访问,因此元素的插入和删除代价较小,只要改变几个指针即可.

浅谈数据结构之图的邻接表深度和广度优先遍历(九)

邻接矩阵是一种不错的图存储结构,但是我们发现,对于边数相对较少的图,这种结构是存在对存储空间的极大浪费的.我们知道,顺序存储结构存在预先分配内存可能造成空间浪费的问题,于是引出了链式存储的结构.同样的,我们也可以考虑对边或弧使用链式存储的方式来避免空间浪费的问题.因此,对于图的存储结构,我们同样引入了一种数组与链表相组合的存储方法,我们一般称之为邻接表. 邻接表的处理方法是这样的:(1).图中顶点用一个一维数组存储,当然,顶点也可以用单链表来存储,不过数组可以较容易的读取顶点的信息,更加方便:另

浅谈数据结构之二叉树存储结构实现(七)

树:是n个结点的有限集:n=0时称为空树.在任意一棵非空树中,有且只有一个特定的结点称为根结点:其余的结点可分为m(m>0)个互不相交的有限集,其中每一个有限集都是一棵子树.结点拥有的子树数称为结点的度:度为0的结点称为叶结点或者终端结点,度不为0的结点称为分支结点或者非终端结点:树的度就是树内各结点的度的最大值. 二叉树的特点有:(1).每个结点最多有两棵子树,所以二叉树不存在度大于2的结点(注意:不是只有两棵子树,而是最多有两棵子树,没有子树或者有一颗子树都是可以的);(2).左子树和右子树

浅谈数据结构:哈希表

一.  基本概念 哈希表(hash table )是一种根据关键字直接访问内存存储位置的数据结构,通过哈希表,数据元素的存放位置和数据元素的关键字之间建立起某种对应关系,建立这种对应关系的函数称为哈希函数 二.哈希表的构造方法 假设要存储的数据元素个数是n,设置一个长度为m(m > n)的连续存储单元,分别以每个数据元素的关键字Ki(0<=i<=n-1)为自变量,通过哈希函数hash(Ki),把Ki映射为内存单元的某个地址hash(Ki),并将数据元素存储在内存单元中 从数学的角度看,哈

浅谈数据结构系列 栈和队列

计算机程序离不开算法和数据结构,在数据结构算法应用中,栈和队列应用你比较广泛,因为两者在数据存放和读取方面效率比较高,本章节重点讲解两者的基本概念和实现. 基本概念 栈:是一种先进后出,后进先出的数据结构,本质上是线性表,只是限制仅允许在表的一段进行插入和删除工作.此端为栈顶,这是在栈中应用很关键的概念.所有数据的处理都是在栈顶进行的,进栈时,栈中元素增加,栈顶上移一位,出栈时栈顶下移一位.应用中比如:洗碗,每次洗干净的碗放在上面-进栈,取碗,从顶上取出一个-出栈:装子弹-进栈,开枪-出栈. 队