That is, given an array a[1...n] of different integer numbers, your program must answer a series of questions Q(i, j, k) in the form: "What would be the k-th number in a[i...j] segment, if this segment was sorted?"
For example, consider the array a = (1, 5, 2, 6, 3, 7, 4). Let the question be Q(2, 5, 3). The segment a[2...5] is (5, 2, 6, 3). If we sort this segment, we get (2, 3, 5, 6), the third number is 5, and therefore the answer to the question is 5.
Input
The second line contains n different integer numbers not exceeding 10 9 by their absolute values --- the array for which the answers should be given.
The following m lines contain question descriptions, each description consists of three numbers: i, j, and k (1 <= i <= j <= n, 1 <= k <= j - i + 1) and represents the question Q(i, j, k).
Output
Sample Input
7 3 1 5 2 6 3 7 4 2 5 3 4 4 1 1 7 3
Sample Output
5 6 3
Hint
题解:
划分树,类似线段树,主要用于求解某个区间的第k 大元素(时间复杂度log(n)),快排本也可以快速找出,但快排会改变原序列,所以每求一次都得恢复序列。
下面就以 POJ 2104 进行解说:
题目意思就是,给你n 个数的原序列,有m 次询问,每次询问给出l、r、k,求原序列l 到r 之间第k 大的数。n范围10万,m范围5千,这道题用快排也可以过,快排过的时间复杂度n*m,而划分树是m*logn(实际上应该是nlogn才对,因为建图时间是nlogn,n又比m大),分别AC后,时间相差很明显。
划分树,顾名思义是将n 个数的序列不断划分,根结点就是原序列,左孩子保存父结点所有元素排序后的一半,右孩子也存一半,也就是说排名1 -> mid的存在左边,排名(mid+1) -> r 的存在右边,同一结点上每个元素保持原序列中相对的顺序。见下图:
红点标记的就是进入左孩子的元素。
当然,一般不会说每个结点开个数组存数,经观察,每一层都包含原本的n 个数,只是顺序不同而已,所以我们可以开val[20][N]来保存,也就是说共20层,每一层N个数。
我们还需要一个辅助数组num,num[i]表示i 前面有多少数进入左孩子(i 和i 前面可以弄成本结点内也可以是所有,两种风格不同而已,下面采取的是本结点内),和val一样,num也开成num[20][N],来表示每一层,i 和i 前面(本结点)有多少进入左孩子。
第一层:1 进入左孩子,num[1]=1,5 进入右孩子,num[2]=1,...,num[8]=4。
第二层:5 进入左孩子,num[5]=1,6 进入右孩子,num[6]=1,...,num[8]=2。
建图时就是维护每一层val[]和num[]的值就可以了。
很是清晰的,
最后那个询问其实不难,自己一开始瞎比比了一会,卡了许久时间。
这样子转换一下,多仔细考虑转换l,r那一段。
1 #include<cstring> 2 #include<cmath> 3 #include<iostream> 4 #include<algorithm> 5 #include<cstdio> 6 7 #define N 100007 8 using namespace std; 9 inline int read() 10 { 11 int x=0,f=1;char ch=getchar(); 12 while(ch<'0'||ch>'9'){if (ch=='-')f=-1;ch=getchar();} 13 while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();} 14 return x*f; 15 } 16 17 int n,m; 18 int a[N],val[21][N],num[21][N]; 19 20 void build(int deep,int l,int r) 21 { 22 if (l==r) return; 23 int mid=(l+r)>>1,same=mid-l+1; 24 for (int i=l;i<=r;i++) 25 if (val[deep][i]<a[mid]) same--; 26 int lh=l,rh=mid+1; 27 for (int i=l;i<=r;i++) 28 { 29 if (i==l) num[deep][i]=0; 30 else num[deep][i]=num[deep][i-1]; 31 if (val[deep][i]<a[mid] || val[deep][i]==a[mid]&&same>0)//没有same那么所以有和a[mid]一样大的树都会进入做子树 32 { 33 val[deep+1][lh++]=val[deep][i]; 34 num[deep][i]++; 35 if (val[deep][i]==a[mid]) same--; 36 } 37 else val[deep+1][rh++]=val[deep][i]; 38 } 39 build(deep+1,l,mid),build(deep+1,mid+1,r); 40 } 41 int query(int deep,int l,int r,int x,int y,int k) 42 { 43 if (l==r) return val[deep][l]; 44 int ly,mid=(l+r)>>1; 45 if (x==l) ly=0; 46 else ly=num[deep][x-1]; 47 int sum=num[deep][y]-ly;//表示放在左边有多少。 48 if (sum>=k) return query(deep+1,l,mid,l+ly,l+num[deep][y]-1,k);//转换到这一段区间放入做子树的那一段。 49 else 50 { 51 int lr=mid+1+(x-l-ly); 52 return query(deep+1,mid+1,r,lr,lr+y-x+1-sum-1,k-sum); 53 } 54 } 55 int main() 56 { 57 while(~scanf("%d%d",&n,&m)) 58 { 59 for (int i=1;i<=n;i++) 60 a[i]=read(),val[1][i]=a[i]; 61 sort(a+1,a+n+1); 62 build(1,1,n); 63 for (int i=1;i<=m;i++) 64 { 65 int l=read(),r=read(),k=read(); 66 printf("%d ",query(1,1,n,l,r,k)); 67 } 68 } 69 }