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

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了浅谈数据结构题的几个非经典解法有感相关的知识,希望对你有一定的参考价值。

主要内容:

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

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;
}

 

  

 





以上是关于浅谈数据结构题的几个非经典解法有感的主要内容,如果未能解决你的问题,请参考以下文章

学习笔记: cdq分治

二进制分组——强制在线的有力算法

整体二分初探 两类区间第K大问题 poj2104 & hdu5412

整体二分总结

java并发编程--一道经典多线程题的2种解法

AcWing1048 鸡蛋的硬度(浅谈两种解法的思考方向)