分块分段

简介:~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

  各类乱七八糟数据结构的本质:定义若干正则集合,并将他们组织成某种合适的结构,而查找算法就是要把查找的结果表示成若干个正则集合的划分,进而在每个正则集合中通过枚举的方式实现查找。

  分块其实就是一种最简单的组织形式——hash

 

  对于一个待处理的区间N,将其等分为 Size = sqrt ( N ) 块。对应每块大小则为Size。

  定其在块中的表现形式为 ( i , j ),其中i为块编号,用于指定其在哪块。j为块中序号,用于指定其在该块中的具体位置。

  易得原序号为id的数对应hash后为( id/Size , id%Size) PS:神似于计算机内存的分块

应用1:~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

  处理对象:指定[ l , r ]区间,求最值

  [ 0 , Size ) [ Size , 2Size )....[ xSize , (x+1)Size )...

   l属于 [ Size , 2Size ) ; r属于 [ xSize , (x+1)Size )

 

  思路:

  预处理每块内部的最值

  遍历包含于[ l ,r ] 区间所有块,即对应块编号2—(x-1),获取比较最值

  暴力搜索[ l , r ] 两端的数值,即对应原id区间:[ l , 2Size ) &[ xSize , r]

 

 

  易证:

  预处理复杂度O(N) ;

   查询复杂度O(sqrt(N))

//预处理:
#define maxn 10005
int num[maxn];
int maxnum[Size+5];
int n,Size;
void init()
{    
    for(int i=0;i<n;i++)
        if(i%Size==0 || num[i]>maxnum[i/Size] ) 
            max[i/Size]=num[i];    
}

 

//查询:
int query(int l,int r) 
{
    int ret=num[l];
    for(int j=l;j<=r;)
    {
        if(j%Size==0 && j+Size-1<=r) 
        {
            if(maxnum[j/Size]>ret) ret=maxnum[j/Size];
            j+=Size;
        }
        else 
        {
            if(num[j]>ret) ret=num[j];
            j++;
        }
    }
    return ret;
}

 

应用2:~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

  处理对象:在应用1的基础上添加更新功能,可修改任意点的值

  思路: 修改该值后同步更新改点所在的块内数据即可 其他同应用1

  更新复杂度O( 1 )

  代码:省略~

应用3:~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

  应用对象:区间第K大

  思路:

  对每个块进行内部排序

  二分分界限值X,对指定区间进行查找<X的数的个数,如区间包含整块则二分,否则暴力。

  直到X满足指定条件(即所得<X的值的个数=K)

  复杂度:log( r-l ) × (N/Size x log(Size)+Size×2)

  即:log(N) sqrt(N)左右,常数略大

  代码:省略~

 

  按之前的理论,复杂度为:log( r-l ) × (N/Size x log(Size)+Size×2)

  其中(N/Size x log(Size)+Size×2) 部分

  Size的值不可能过大或过小,只能在sqrt(N)左右不定,否则都有极端数据能卡掉,所以这部分的复杂度平均为sqrt(N)

  如果查询次数过多,分分钟T掉。得优化!

 

 

  假想,如果每次查询的边界恰好为我Size的区间端点,我可直接二分查找,直接去掉两端的暴力过程。

  由此想到将区间进行分段,即分作长度不同的块,可对于不同的数据,最优的分段方法就不一定相同。我们最好可将所有分段方法保留下来,但这么搞内存显然不够。

 

  一个数必定可表示为2进制,对应2进制数的长度也不会太长。

  由此联想,对于任意一段区间,都可拆分成多项大小为2的次幂的块。

  我们只需预处理出所有大小为2的次幂的块,每次查询就是在各块内部进行二分查找。

  根据输入数据大小,得到足够多的层,每层对应一种固定大小的块。 各块内部自行排序之后即可。

  总体复杂度:

  预处理:层数 * Nlog(N)

  查询:log(N)^2

  POJ 2104:

#include <stdio.h>
#include <iostream>
#include <algorithm>
using namespace std;
#define maxn 100005
const int X = 18;
int n,m;
int arr[maxn];
int sorted[20][maxn];
int countfind(int x,int l,int r,int val)
{// 找出第x层,区间为l,r的块中有多少数 < val
    int *sorted = ::sorted[x];
    if(sorted[l]>=val) return 0;
    if(sorted[r]<val) return r-l+1;
    int st=l;
    while(l+1<r)
    {
        int mid=(l+r)>>1;
        if(sorted[mid]<val) l=mid;
        else r=mid;
    }
    return r-st;
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=0;i<n;i++) scanf("%d",&arr[i]);

    for(int j=0;j<X;j++)
    for(int i=0;i<n;i++)
    sorted[j][i]=arr[i];
// 预处理每层大小为 2,4,8,16... 的块
    for(int j=1;j<X;j++)
    {
        int step=1<<j;
        for(int i=0;i+step-1<n;i+=step)
        sort(sorted[j]+i,sorted[j]+i+step);
    }
    while(m--)
    {
        int l,r,k;
        scanf("%d%d%d",&l,&r,&k);
        l--;
        r--;
        int mi=-1e9-1;
        int ma=1e9+1;
        while(mi+1<ma)
        {
            int ans=0;
            int mid=(mi+ma)>>1;
            for(int i=l;i<=r;)
            {//此处相当于将一个数分成2的幂的和
                for(int j=X;j>=0;j--)
                {
                    int step=1<<j;
                    if(i%step==0 && i+step-1<=r)
                    {
                        ans+=countfind(j,i,i+step-1,mid);
                        i+=step;
                        break;
                    }
                }
            }
            if(ans<k) mi=mid;
            else ma=mid;
        }
        printf("%d\n",mi);
    }
    return 0;
}

修改自神秘链接:http://www.cnblogs.com/sweetsc/archive/2012/08/15/2639395.html

posted @ 2016-02-20 14:56  蓦辰  阅读(651)  评论(0编辑  收藏  举报