划分树学习笔记

    今天没事就去刷以前hdu做过但是没过的题,前面的题现在还是能过的,做到这题就卡了,传说中的划分树,只闻其名未见其身。然后搜索了一下划分树的资料,擦擦擦,这不就是同快排的原理+线段树的操作,两者一融合进化成了划分树么。前面两个都会,学习起来倍感轻松。

 建树过程: 先对区间[1,n]内所有元素进行排序,未排序之前的数列赋值给线段树的第一层元素(tree[0][i]),然后就是同快排的原理以排序后中间元素为参照,小于它的放在树下一层的左边,大于它的放在树下一层的右边(划分树建树以中间元素为参照,快排以第一关键元素为参照)。然后再开一个sum数组,sum[d][i]表示第d层前i个元素有多少个元素小于as[mid](as[mid]为排序后的中间值),这样一层一层建树下去最后建完树后等于对原数列排好了序。

查询过程: 这里最关键了。在查找区间[tl,tr]时,往下查询[tl,tr]左右孩子时,都要对区间[tl,tr]进行更新。

定义两个数s, ss,  d表示第d层, k(k表示要查询的第k元素) :

s 表示区间[l,tl]有多少个元素小于as[mid],  s=sum[d][tl-1];

ss 表示区间[tl,tr]有多少个元素小于as[mid], ss=sum[d][tr]-s;

if(ss>=k)  则下一层查询区间为[l+s,l+s+ss-1];

else  则下一层查询区间为[mid+1+tl-l-s,mid+1+tr-l-s-ss];

自己懒,不愿画图多解释,借用一下小媛姐姐的图。

分析一下算法复杂度: 建树nlogn+查询mlogn,很强大的说。

题目链接:hdu2665 kth number

题目大意:O(-1)

解题思路:O(-1)

View Code
 1 #include <iostream>
 2 #include <cstdio>
 3 #include <cstring>
 4 #include <algorithm>
 5 using namespace std;
 6 
 7 const int maxn=100005;
 8 int sum[20][maxn], tree[20][maxn];
 9 int a[maxn], as[maxn]; ///原数组, 排序后数组
10 
11 void Build(int d, int l, int r)
12 {
13     int mid=(l+r)>>1, lp=l, rp=mid+1, lm=mid-l+1;
14     for(int i=l; i<=mid; i++)
15         if(as[i]<as[mid]) lm--; ///!!! 先假设前mid-l+1个数都等于as[mid],as[i]比它小则减1
16     for(int i=l; i<=r; i++)
17     {
18         if(i==l) sum[d][i]=0;   ///sum[d][i]表示第d层前i个数有多少个小于as[mid]
19         else sum[d][i]=sum[d][i-1];
20         if(tree[d][i]==as[mid])
21         {
22             if(lm) lm--, sum[d][i]++, tree[d+1][lp++]=tree[d][i];
23             else tree[d+1][rp++]=tree[d][i];
24         }
25         else if(tree[d][i]<as[mid]) sum[d][i]++, tree[d+1][lp++]=tree[d][i];
26         else tree[d+1][rp++]=tree[d][i];
27     }
28     if(l==r) return ;
29     Build(d+1,l,mid);
30     Build(d+1,mid+1,r);
31 }
32 
33 int Query(int d, int l, int r, int tl, int tr, int k)
34 {
35     int s, ss, mid=(l+r)>>1;
36     if(l==r) return tree[d][l];
37     if(l==tl) s=0, ss=sum[d][tr]; ///!!特判
38     else s=sum[d][tl-1], ss=sum[d][tr]-s;
39     if(ss>=k) return Query(d+1,l,mid,l+s,l+s+ss-1,k);
40     else return Query(d+1,mid+1,r,mid+1+tl-l-s,mid+1+tr-l-s-ss,k-ss);
41 }
42 
43 int main()
44 {
45     int T, n, m;
46     cin >> T;
47     while(T--)
48     {
49         scanf("%d%d",&n,&m);
50         for(int i=1; i<=n; i++)
51         {
52             scanf("%d",a+i);
53             tree[0][i]=as[i]=a[i];
54         }
55         sort(as+1,as+n+1);
56         Build(0,1,n);
57         while(m--)
58         {
59             int l, r, k;
60             scanf("%d%d%d",&l,&r,&k);
61             int ans=Query(0,1,n,l,r,k);
62             printf("%d\n",ans);
63         }
64     }
65     return 0;
66 }

题目链接:hdu3743  minimum sum

题目大意:给你一个有n个数的数列,然后有m次询问[l,r],让你在[l,r]中找一个数x使得|Xi-x|最小(l<=i<=r).

解题思路:初中知识可知,形如|Xi-x|最小,那么必定是找排序后区间的中位数了。区间[l,r]的中位数必定是第k((r-l+2)/2)位。

假设一段区间排序后是x1,x2,x3,x4,x5,x6,x7,x4是中位数,那么题要求的答案不就是(x4-x1)+(x4-x2)+(x4-x3)+(x5-x4)+(x6-x4)+(x7-x4)=(x5+x6+x7)-(x1+x2+x3)。   

结果分为了比中位数大的数和比中位数小的数,啊哈,这不就是要我们求第k元素么,这个k值固定了而已(中位数)。这里需要多开一个数组tol来记录第d层前i个数的和。当查询到第d层时,如果所求的ss比k大,则结果ans加上被分到右边的数,向左下层继续查询。如果ss比k小,则结果ans减去分到左边的数,向右下层继续查询。

注意区间奇偶判定以及数的范围。

View Code
 1 #include <iostream>
 2 #include <cstdio>
 3 #include <cstring>
 4 #include <algorithm>
 5 using namespace std;
 6 
 7 const int maxn=100005;
 8 typedef long long lld;
 9 int sum[20][maxn], tree[20][maxn];
10 lld tol[20][maxn];  ///!!!
11 int a[maxn], as[maxn];
12 
13 void Build(int d, int l, int r)
14 {
15     int mid=(l+r)>>1, lp=l, rp=mid+1, lm=mid-l+1;
16     for(int i=l; i<=r; i++)
17     {
18         if(as[i]<as[mid]) lm--;
19         if(i==l) tol[d][i]=tree[d][i];
20         else tol[d][i]=tol[d][i-1]+tree[d][i];
21     }
22     for(int i=l; i<=r; i++)
23     {
24         if(i==l) sum[d][i]=0;
25         else sum[d][i]=sum[d][i-1];
26         if(tree[d][i]==as[mid])
27         {
28             if(lm) lm--, sum[d][i]++, tree[d+1][lp++]=tree[d][i];
29             else tree[d+1][rp++]=tree[d][i];
30         }
31         else if(tree[d][i]<as[mid]) sum[d][i]++, tree[d+1][lp++]=tree[d][i];
32         else tree[d+1][rp++]=tree[d][i];
33     }
34     if(l==r) return ;
35     Build(d+1,l,mid);
36     Build(d+1,mid+1,r);
37 }
38 
39 lld Query(int d, int l, int r, int tl, int tr, int k, lld &ret)
40 {
41     if(l==r) return tree[d][l];
42     int mid=(l+r)>>1, s, ss, ql, qr;
43     if(tl==l) s=0, ss=sum[d][tr];
44     else s=sum[d][tl-1], ss=sum[d][tr]-s;
45     int x=tl-l-s, xx=tr-tl+1-ss;
46     if(ss>=k)
47     {
48         ql=mid+1+x, qr=ql+xx-1;
49         if(xx>0) ret+=tol[d+1][qr]-(x>0?tol[d+1][ql-1]:0);
50         return Query(d+1,l,mid,l+s,l+s+ss-1,k,ret);
51     }
52     else
53     {
54         ql=l+s, qr=l+s+ss-1;
55         if(ss>0) ret-=tol[d+1][qr]-(s>0?tol[d+1][ql-1]:0);
56         return Query(d+1,mid+1,r,mid+1+x,mid+1+x+xx-1,k-ss,ret);
57     }
58 }
59 
60 int main()
61 {
62     int n, m, T, tcase=0;
63     cin >> T;
64     while(T--)
65     {
66         scanf("%d",&n);
67         for(int i=1; i<=n; i++)
68         {
69             scanf("%d",a+i);
70             tree[0][i]=as[i]=a[i];
71         }
72         sort(as+1,as+n+1);
73         Build(0,1,n);
74         scanf("%d",&m);
75         printf("Case #%d:\n",++tcase);
76         while(m--)
77         {
78             int l, r;
79             scanf("%d%d",&l,&r);
80             lld ret=0, len=r-l+1;
81             lld tp=Query(0,1,n,l+1,r+1,(len+1)/2,ret);
82             if(len%2==0) ret-=tp;
83             printf("%I64d\n",ret);
84         }
85         puts("");
86     }
87     return 0;
88 }
原文地址:https://www.cnblogs.com/kane0526/p/3033212.html