分块分段
简介:~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
各类乱七八糟数据结构的本质:定义若干正则集合,并将他们组织成某种合适的结构,而查找算法就是要把查找的结果表示成若干个正则集合的划分,进而在每个正则集合中通过枚举的方式实现查找。
分块其实就是一种最简单的组织形式——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