zoukankan      html  css  js  c++  java
  • 线段树总结

    高级数据结构——线段树总结

    本蒟蒻最近在做线段树的题,做了一小部分,有感而发,故写下这篇博客,如有错误,请大佬指出。

    线段树,作为一种高级数据结构,而其作用与分块、树状数组均有一脉相承的部分,而且有的题均可以使用上面的两种算法去解决(当然只是一部分题)
    对于线段树的介绍,我也就不再多说了,即对于数列将其放在树上进行维护一些值,在题目的要求下进行修改和更新,最后得到答案。

    首先我们先上一两道模板题

    1、洛谷P3372线段树1(加操作)

    这是一道十分经典的线段树模板,当然他的解法也有其他方法,(十分重要)。
    下面是代码:

    #include<bits/stdc++.h>
    #define ll long long
    const int N=5e6+8;
    using namespace std;
    ll n,m,tag[N],ans[N],a[N];
    ll ls(ll x){
        return x<<1;
    }//左儿子扩展 (x*2) 
    ll rs(ll x){
        return x<<1|1;
    }//右儿子扩展(x*2+1) 
    void up(ll x){
        ans[x]=ans[ls(x)]+ans[rs(x)];
    }// 求出当前点的总和值 
    void build(ll l,ll r,ll p){
        if(l==r){
            ans[p]=a[l];
            return;
        }
        ll mid=(l+r)>>1;
        build(l,mid,ls(p));//左儿子建树 
        build(mid+1,r,rs(p));//右儿子建树
        up(p);//进行整合 
    }
    void f(ll l,ll r,ll p,ll k){//k是懒(延迟)标记的数值 
        tag[p]+=k;
        ans[p]+=(r-l+1)*k;
    }//tag[]懒标记点。
    void down(ll l,ll r,ll p){
        ll mid=(l+r)>>1;
        f(l,mid,ls(p),tag[p]);//左传 
        f(mid+1,r,rs(p),tag[p]);//右传 
        tag[p]=0;//懒标记传递结束,清零 
    } 
    void xg(ll xl,ll xr,ll l,ll r,ll p,ll k){//修改操作 
        if(l>=xl&&xr>=r){//当前区间被所查询的区间包含 
            ans[p]+=(r-l+1)*k;
            tag[p]+=k;
            return ; 
        }
        down(l,r,p);//懒坐标传递 
        ll mid=(l+r)>>1;
        if(xl<=mid){
            xg(xl,xr,l,mid,ls(p),k);	
        }
        if(xr>mid){
            xg(xl,xr,mid+1,r,rs(p),k);
        } 
        up(p);//更新该节点 
    } 
    ll query(ll gl,ll gr,ll l,ll r,ll p){
        ll an=0;
        if(gl<=l&&gr>=r){
            return ans[p];
        }
        ll mid=(l+r)>>1;
        down(l,r,p);
        if(gl<=mid){
            an+=query(gl,gr,l,mid,ls(p));
        }
        if(gr>mid){
            an+=query(gl,gr,mid+1,r,rs(p));
        }
        return an;
    }
    ll fl,x,y,z;
    int main(){
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++){
            scanf("%lld",&a[i]);
        }		
        build(1,n,1);
        for(int i=1;i<=m;i++){
            scanf("%d",&fl);
            if(fl==1){
                scanf("%lld%lld%lld",&x,&y,&z);
                xg(x,y,1,n,1,z);	
            }
            if(fl==2){
                scanf("%lld%lld",&x,&y);
                printf("%lld
    ",query(x,y,1,n,1));
            }
        }
        return 0;
    } 
    

    下一道模板题:

    2、洛谷P3373线段树2(加乘操作)

    这道题与上一道题同理,up和down的部分需要修改一下即可。(再给大家一道双倍经验题(P2023 [AHOI2009]维护序列

    下面就是代码:

    #include<bits/stdc++.h>
    #define ll long long 
    using namespace std;
    const int N=5e6+7;
    ll ans[N],a[N],at[N],ml[N];
    ll fl,n,m,P,x,y,z;
    ll ls(ll x){
    	return x<<1;
    }
    ll rs(ll x){
    	return x<<1|1;
    }
    void up(ll p){
    	ans[p]=(ans[ls(p)]+ans[rs(p)])%P;
    }
    void build(ll l,ll r,ll p){
    	at[p]=0;
    	ml[p]=1;
    	if(l==r){
    		ans[p]=a[l];
    		return;
    	}
    	ll mid=(l+r)>>1;
    	build(l,mid,ls(p));
    	build(mid+1,r,rs(p));
    	up(p);
    }
    void down(ll l,ll r,ll p){
    	ml[ls(p)]=(ml[ls(p)]*ml[p])%P;
    	ml[rs(p)]=(ml[rs(p)]*ml[p])%P;
    	at[ls(p)]=(at[ls(p)]*ml[p])%P;
    	at[rs(p)]=(at[rs(p)]*ml[p])%P;
    	ans[ls(p)]=(ans[ls(p)]*ml[p])%P;
    	ans[rs(p)]=(ans[rs(p)]*ml[p])%P;
    	ml[p]=1;//乘懒标记传递完成,清零处理,乘法优先于加法,所以先计算乘法再计算加法 
    	ll mid=(l+r)>>1;
    	at[ls(p)]=(at[ls(p)]+at[p])%P;
    	at[rs(p)]=(at[rs(p)]+at[p])%P;
    	ans[ls(p)]=(ans[ls(p)]+(mid-l+1)*at[p])%P;
    	ans[rs(p)]=(ans[rs(p)]+(r-mid)*at[p])%P;
    	at[p]=0; 
    }
    void xg(ll gl,ll gr,ll l,ll r,ll p,ll k){
    	if(l>=gl&&gr>=r){
    		at[p]=(k+at[p])%P;
    		ans[p]=((r-l+1)*k+ans[p])%P;
    		return ;
    	}
    	down(l,r,p);
    	ll mid=(l+r)>>1;
    	if(gl<=mid){
    		xg(gl,gr,l,mid,ls(p),k);
    	}
    	if(gr>mid){
    		xg(gl,gr,mid+1,r,rs(p),k);
    	}
    	up(p);
    }//修改+; 
    void ch(ll cl,ll cr,ll l,ll r,ll p,ll k){
    	if(l>=cl&&r<=cr){
    		ml[p]=(ml[p]*k)%P;
    		at[p]=(at[p]*k)%P;
    		ans[p]=(ans[p]*k)%P;
    		return ;
    	}
    	down(l,r,p);
    	ll mid=(l+r)>>1;
    	if(cl<=mid){
    		ch(cl,cr,l,mid,ls(p),k);
    	}
    	if(cr>mid){
    		ch(cl,cr,mid+1,r,rs(p),k);
    	}
    	up(p);
    }//修改*; 
    ll query(ll al,ll ar,ll l,ll r,ll p){
    	ll an=0;
    	if(al<=l&&ar>=r){
    		return ans[p]%P;
    	}
    	down(l,r,p);
    	ll mid=(l+r)>>1;
    	if(al<=mid){
    		an=an+query(al,ar,l,mid,ls(p));
    	}
    	if(ar>mid){
    		an=an+query(al,ar,mid+1,r,rs(p));
    	}
    	return an%P;
    }
    int main(){
    	scanf("%lld%lld",&n,/*&m,*/&P);
    	for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
    	sacnf("%lld",&m);
    	build(1,n,1);
    	for(int i=1;i<=m;i++){
    		scanf("%lld",&fl);
    		if(fl==1){
    			scanf("%lld%lld%lld",&x,&y,&z);
    			ch(x,y,1,n,1,z);
    		}
    		if(fl==2){
    			scanf("%lld%lld%lld",&x,&y,&z);
    			xg(x,y,1,n,1,z);
    		}
    		if(fl==3){
    			scanf("%lld%lld",&x,&y);
    			printf("%lld
    ",(query(x,y,1,n,1))%P);
    		}
    	}
    	
    	return 0;
    }
    

    当你写完这三道模板题之后那么恭喜你,你已经线段树入门了。
    下面,就是对于线段树的进一步的运用。

    使用线段树维护方差,平均数等操作

    3、P1471 方差

    这道题的题面我就不再进行粘贴,具体的意思大概如下:
    给定一个数列,其中有三个操作,
    1、将L到R的区间内的数加上k。
    2、查询L到R区间的平均数。
    3、查询L到R区间的方差。
    我表示这道题第一开始看的时候对于操作1,2基本就是十分木板的操作,但是当我看到第三个操作时,我有点蒙。
    但是,将方差的公式进行展开,就可以得到一个十分明晰的思路

    [egin{aligned} sum_{i=1}^{n}left(x_{i}-overline{x} ight)^{2} &=sum_{i=1}^{n}left(x_{i}^{2}-2 x_{i} overline{x}+overline{x}^{2} ight) \ &=sum_{i=1}^{n} x_{i}^{2}+n overline{x}^{2}-2 overline{x} sum_{i=1}^{n} x_{i} \ &=sum_{i=1}^{n} x_{i}^{2}+n overline{x}^{2}-2 overline{x} cdot n overline{x} \ &=sum_{i=1}^{n} x_{i}^{2}-overline{n} overline{x}^{2} end{aligned} ]

    最后我们再将这个公式除上n得到最终的式子,这样我们就有了初步的思路,维护这个序列的两个值一个是序列和,一个是序列的平方和。这样我们就可以得到我们所求的答案。
    现在我们来考虑如何使用线段树来维护平方和。
    我们首先将平方和的公式展开

    [(x+k)^{2}=x^{2}+2 k x+k^{2} ]

    我们可以知道这样就可以维护平方和:
    代码:

    #include <bits/stdc++.h> 
    using namespace std;
    const int N=1e6+10;
    inline int read(){
    	int x=0,f=1;
    	char ch=getchar();
    	while(ch<'0'||ch>'9'){
    		if(ch=='-'){
    			f=-1;
    		} 
    		ch=getchar();
    	}
    	while(ch>='0'&&ch<='9'){
    		x=(x<<1)+(x<<3)+(ch^48);
    		ch=getchar(); 
    	}
    	return x*f;
    } 
    struct node{
    	int l,r,siz;
    	double ave,sum,tag;
    }t[N];
    int n,m;
    double a[N]; 
    inline int ls(int x){return x<<1;}
    inline int rs(int x){return x<<1|1;}
    inline void upd(int x){
    	t[x].ave=t[ls(x)].ave+t[rs(x)].ave;   
    	t[x].sum=(t[ls(x)].sum+t[rs(x)].sum);
    }
    inline void build(int l,int r,int x){
    //	cout<<1;
    	t[x].l=l,t[x].r=r,t[x].siz=r-l+1;
    	if(l==r){
    		t[x].ave=a[l];
    		t[x].sum=a[l]*a[l];
    		return ;
    	}
    	int mid=(l+r)>>1;
    	build(l,mid,ls(x));
    	build(mid+1,r,rs(x));
    	upd(x);
    }
    inline void pushdown(int x){
    	if(!t[x].tag) return;
    	t[ls(x)].tag+=t[x].tag;
    	t[rs(x)].tag+=t[x].tag;
    	t[ls(x)].sum+=(2.0*t[ls(x)].ave*t[x].tag+t[ls(x)].siz*t[x].tag*t[x].tag);//维护平方和
    	t[rs(x)].sum+=(2.0*t[rs(x)].ave*t[x].tag+t[rs(x)].siz*t[x].tag*t[x].tag);
    	t[ls(x)].ave+=t[x].tag*t[ls(x)].siz;
    	t[rs(x)].ave+=t[x].tag*t[rs(x)].siz;
    	t[x].tag=0;
    	return ;
    }
    inline void add(int l,int r,int x,double k){
    	if(t[x].l>=l&&t[x].r<=r){
            t[x].sum+=(2.0*t[x].ave*k+t[x].siz*k*k);
    	    t[x].ave+=k*t[x].siz;
    		t[x].tag+=k;
    		return ;
    	}
    	pushdown(x);
    	int mid=(t[x].l+t[x].r)>>1;
    	if(l<=mid){
    		add(l,r,ls(x),k);
    	}
    	if(r>mid){
    		add(l,r,rs(x),k);
    	}
    	upd(x);
    }
    inline double query(int l,int r,int x,bool flag){
    	if(t[x].l>=l&&t[x].r<=r){
    		if(flag==1){
    		return t[x].ave;}
    		else return t[x].sum;
    	}   
    	pushdown(x);
    	int mid=(t[x].l+t[x].r)>>1; 
    	double res=0;
    	if(l<=mid){
    		res+=query(l,r,ls(x),flag);
    	}
    	if(r>mid){
    		res+=query(l,r,rs(x),flag); 
    	} 
    	return res;
    }
    int  main(){
    	scanf("%d%d",&n,&m); 
    	for(int i=1;i<=n;i++){
    		scanf("%lf",&a[i]);
    	}
    	build(1,n,1); 
    
    	while(m--){
    		int op,l,r;
    		double k;
    		double ans1,ans2;
    		op=read();
    		if(op==1){
    		    scanf("%d%d%lf",&l,&r,&k); 	  
    			add(l,r,1,k);	  		
    		}
    		if(op==2){
    			scanf("%d%d",&l,&r);
    			ans1=query(l,r,1,1);	  
    			printf("%.4lf",(double)ans1/(r-l+1));
    			puts("");
    		}	
    		if(op==3){
    			scanf("%d%d",&l,&r);	  
    			ans1=query(l,r,1,1);
    			ans2=query(l,r,1,0);	  
    			printf("%.4lf",(double)ans2/(r-l+1)-ans1/(r-l+1)*ans1/(r-l+1));
    			puts("");
    		}   	
    	
    	}	
    	return 0;
    }
    

    值得注意的是这道题的题目要求是实数范围内,所以千万千万不要忘掉使用,

    double(手动滑稽

    这就是线段树去维护不同值的一个十分好的题,也十分的考验思维。
    下面,我们看下一题,一道维护0/1序列的题。

    4、P2894 [USACO08FEB]酒店Hotel (连续的0/1序列问题)

    首先我们看一下题目速所要求的操作:

    1、查询房间,给出一个x,查询是否有一个序列可以满足连续的为0的序列,如果有,则输出序列最左端的序号,使这个序号尽可能的小,若不存在,则输出0。

    2、给定x,y表示退房。

    看到了所给的操作,我们需要去考虑,一个连续的区间在查询时可以分为3种情况。
    1、连续的区间在左儿子中。
    2、连续的区间在右儿子中。
    3、连续的区间在左右儿子之间。
    所以,我们就需要取分别维护这三个值,需要的时候,进行合并。
    从左开始的最长,从右开始的最长,和整个区间的最长部分。
    对于upd来说,我们需要每一次去分类讨论是否可以合并。
    而对于down来说,就直接更新即可。

    下面就是代码:

    #include<bits/stdc++.h>
    using namespace std;
    template<typename type>
    void scan(type &x){
        type f=1;x=0;char s=getchar();
        while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
        while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
        x*=f;
    }
    #define itn int
    const int N=1e6+7;
    struct node{
        int l,r;
        int suml,sumr,sum,siz,tag;
    }t[N];
    itn n,m;
    #define il inline
    il int ls(int x){return x<<1;}
    il int rs(int x){return x<<1|1;}
    void upd(int x){
        t[x].sum=max(t[ls(x)].sum,t[rs(x)].sum);
        t[x].sum=max(t[x].sum,t[ls(x)].sumr+t[rs(x)].suml);
        t[x].suml=t[ls(x)].suml;
        if(t[ls(x)].suml==t[ls(x)].siz){
            t[x].suml+=t[rs(x)].suml;
        }
        t[x].sumr=t[rs(x)].sumr;
        if(t[rs(x)].sumr==t[rs(x)].siz){
    		t[x].sumr+=t[ls(x)].sumr;
    	}
    }
    void build(int l,int r,int x){
        t[x].l=l;t[x].r=r;t[x].siz=r-l+1;
        t[x].sumr=t[x].suml=t[x].sum=r-l+1;
        if(l==r){
            return ;
        }
        int mid=(l+r)>>1;
        build(l,mid,ls(x));
        build(mid+1,r,rs(x));
        upd(x);
    }
    void down(int x){
        if(!t[x].tag)return ;
        if(t[x].tag==1){
            t[ls(x)].sum=t[ls(x)].suml=t[ls(x)].sumr=0;
            t[rs(x)].sum=t[rs(x)].suml=t[rs(x)].sumr=0;
            t[ls(x)].tag=t[rs(x)].tag=1;
        }
        if(t[x].tag==2){
            t[ls(x)].tag=t[rs(x)].tag=2;
            t[ls(x)].sum=t[ls(x)].suml=t[ls(x)].sumr=t[ls(x)].siz;
            t[rs(x)].sum=t[rs(x)].suml=t[rs(x)].sumr=t[rs(x)].siz;
        }
        t[x].tag=0;
    }
    void add(int l,int r,int x,int k){
        if(t[x].l>=l&&t[x].r<=r){
            t[x].tag=k;
            if(k==1){
                t[x].sum=t[x].suml=t[x].sumr=0;//全部清空
            }else{
                t[x].sum=t[x].suml=t[x].sumr=t[x].siz;
            }
            return; 
        }
        down(x);
        int mid=(t[x].l+t[x].r)>>1;
        if(l<=mid){
            add(l,r,ls(x),k);
        }
        if(r>mid){
            add(l,r,rs(x),k);
        }
        upd(x);
    }
    itn query(itn l,int r,int x,int k){
        if(l==r){
            return l;
        }
        down(x);
        int mid=(t[x].l+t[x].r)>>1;
        if(t[ls(x)].sum>=k){
            return query(l,mid,ls(x),k);
        }
        if(t[ls(x)].sumr+t[rs(x)].suml>=k){
            return mid-t[ls(x)].sumr+1;
        }else{
            return query(mid+1,r,rs(x),k);
        }
    }
    
    int main(){
        scan(n);scan(m);
        build(1,n,1);
        for(int i=1;i<=m;i++){
            itn s;
            int x,y;
            scan(s);
            scan(x);
            if(s==1){
                if(t[1].sum<x){
                    puts("0");
                    continue;
                }
                itn res=query(1,n,1,x);
                printf("%d
    ",res);
                add(res,res+x-1,1,1);//查询之后不要往届修改
            }
            if(s==2){
                scan(y);
                add(x,x+y-1,1,2);
    
            }
        }
    
        return 0;
    }
    

    5、P2846 [USACO08NOV]光开关Light Switching

    题目大意:
    给你一列灯,让你去开关,然后去查询开着灯的数量
    这里,我们就可以将灯的开关抽象为0和1,
    0表示这个灯是关着的。
    1表示这个灯是开着的。
    其中给出的两步操作:

    1、给出l,r,将l到r之间的数列全部进行翻转。

    2、查询l到r之间的1的个数。

    这样,我们可以十分简单去维护一个区间内0的个数,和1的个数。
    具体操作如下:

    #include<bits/stdc++.h>
    using namespace std;
    #define itn int
    const int N=1e7+7;
    struct node{
        int l,r,sum1,sum0,tag;
    }t[N];
    int ls(int x){return x<<1;}
    int rs(int x){return x<<1|1;}
    void upd(int x){
        t[x].sum1=t[ls(x)].sum1+t[rs(x)].sum1;
        t[x].sum0=t[ls(x)].sum0+t[rs(x)].sum0;
    }
    void build(int l,int r,int x){
        t[x].l=l;t[x].r=r;t[x].tag=0;
        if(l==r){
            t[x].sum0=r-l+1;
            t[x].sum1=0;
            return;
        }
        int mid=(l+r)>>1;
        build(l,mid,ls(x));
        build(mid+1,r,rs(x));
        upd(x);
    }
    void down(int x){
        if(t[x].tag){
            if(t[ls(x)].tag==0){
                t[ls(x)].tag=1;
            }else{
                t[ls(x)].tag=0;
            }
            if(t[rs(x)].tag==0){
                t[rs(x)].tag=1;
            }else{
                t[rs(x)].tag=0;
            }
            swap(t[rs(x)].sum1,t[rs(x)].sum0);
            swap(t[ls(x)].sum1,t[ls(x)].sum0);
            t[x].tag=0;
        }
    }
    void add(itn l,int r,int x){
        if(t[x].l>=l&&t[x].r<=r){
            swap(t[x].sum1,t[x].sum0);
            t[x].tag++;
            t[x].tag%=2;
            return;
        }
        down(x);
        int mid=(t[x].l+t[x].r)>>1;
        if(l<=mid)add(l,r,ls(x));
        if(r>mid)add(l,r,rs(x));
        upd(x);
        return;
    }
    int query(int l,int r,int x){
        if(t[x].l>=l&&t[x].r<=r){
            return t[x].sum1;
        }
        int res=0;
        down(x);
        int mid=(t[x].l+t[x].r)>>1;
        if(l<=mid)res+=query(l,r,ls(x));
        if(r>mid)res+=query(l,r,rs(x));
        return res;
    }	
    int n,m;
    
    int main(){
        scanf("%d%d",&n,&m);
        build(1,n,1);
        for(int i=1;i<=m;i++){
            int ch,x,y;
            scanf("%d%d%d",&ch,&x,&y);
            if(ch==0){
                add(x,y,1);
            }else{
                printf("%d
    ",query(x,y,1));
            }
        }
        return 0;
    }
    
    

    这道题使用的思想非常巧妙,我们也用这种的处理方法去解决这样的问题。

    最后,我们再来看一道比较暴力的题。

    6、P4145 上帝造题的七分钟2 / 花神游历各国

    这道题的题面十分的简单易懂就是给你一个序列,然后让你进行操作。

    1、给出l到r,对于序列之间的数进行开方

    2、查询l到r的区间和

    看完操作,我们好像发现这个并不是特别难的样子,但是我们去思考,怎么去修改,区间维护的话,好像不可行的样子啊qwq
    这时,我们再回过来看一看这个数1e12,当他开6次根号时,我们就会得到这个:


    向下取整之后我们便得到1。
    所以,我们就可以考虑使用暴力修改的方式来进行维护。这样我们就可以很好地解决这种的问题。
    下面是代码:

    #include<bits/stdc++.h>
    using namespace std;
    template <typename type>
    void scan(type &x){
        type f=1;x=0;char s=getchar();
        while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
        while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
        x*=f;
    }
    const int N=1e6+7;
    #define int long long
    struct node{
        int l,r,sum,maxx;
    }t[N<<2];
    int a[N];
    int ls(int x){return x<<1;}
    int rs(int x){return x<<1|1;}
    void upd(int x){
        t[x].sum=t[ls(x)].sum+t[rs(x)].sum;
        t[x].maxx=max(t[ls(x)].maxx,t[rs(x)].maxx);
    }
    void build(int l,int r,int x){
        t[x].l=l;t[x].r=r;
        if(l==r){
            t[x].sum=t[x].maxx=a[l];
            return;
        }
        int mid=(l+r)>>1;
        build(l,mid,ls(x));
        build(mid+1,r,rs(x));
        upd(x);
    }
    void add(int l,int r,int x){
        if(t[x].l==t[x].r){
            t[x].sum=sqrt(t[x].sum);
            t[x].maxx=sqrt(t[x].maxx);
            return;
        }
        int mid=(t[x].l+t[x].r)>>1;
        if(l<=mid&&t[ls(x)].maxx>1)add(l,r,ls(x));
        if(r>mid&&t[rs(x)].maxx>1)add(l,r,rs(x));
        upd(x);
    }
    int query(int l,int r,int x){
        int res=0;
        if(l<=t[x].l&&r>=t[x].r){
            return t[x].sum;
        }
        int mid=(t[x].l+t[x].r)>>1;
        if(l<=mid)res+=query(l,r,ls(x));
        if(r>mid)res+=query(l,r,rs(x));
        return res;
    }
    int n,m;
    signed main(){
        scan(n);
        for(int i=1;i<=n;i++){
            scan(a[i]);
        }
        build(1,n,1); 
        scan(m);
        for(int i=1;i<=m;i++){
            int s,l,r;
            scan(s);scan(l);scan(r);
            if(l>r)swap(l,r);
            if(s==0){
                add(l,r,1);
            }
            if(s==1){
                printf("%lld
    ",query(l,r,1));
            }
        }
        return 0;
    }
      
    

    关于线段树的内容基本就差不多了,后续有什么题我做到之后,会看情况添加进去。
    如果有什么错误,还请各位大佬提出(主要是本人太菜了)qwq

  • 相关阅读:
    什么是线程安全和线程不安全
    C# 实现Dictionary数据对象的深度拷贝
    数据库设计三大范式
    Socket 短连接、长连接
    第二篇:MongoDB高级查询
    如何在oracle中导入导出dmp数据库文件
    以太网中的UDP编程:udp分包问题
    CocosBuilder 值得关注的一个新项目
    [转载]iPhone程序到iPad程序的移植问题
    DWR使用总结
  • 原文地址:https://www.cnblogs.com/xishirujin/p/11378952.html
Copyright © 2011-2022 走看看