归并树 划分树 可持久化线段树(主席树) 入门题 hdu 2665

如果题目给出1e5的数据范围,,以前只会用n*log(n)的方法去想

今天学了一下两三种n*n*log(n)的数据结构

他们就是大名鼎鼎的 归并树 划分树 主席树,,,,

首先来说两个问题,,区间第k大 ,,,,

这个问题的通用算法是 划分树,,

说白一点就是把快速排序的中间结果存起来,

举个栗子

原数列  4  1  8  2  6  9  5  3  7

sorted  1  2  3  4  5  6  7  8  9

。。。。。。。。。。。。。。。。。。。。。。。。。。。

qs[0]  4  1  8  2  6  9  5  3  7

qs[1]  4  1  2  5  3.....8  6  9  7

qs[2]  1  2  3.....4  5......6  7.....8  9

qs[3]  1  2.....3.....4.....5......6......7.....8.....9

qs[4]  1.....2

快速排序的过程如上

我们要求【2,8】区间里第4小的数

我们可以很轻易的知道qs[0]中【2,8】中的数被快排分到左边的个数

记这个个数为 rs  rs=4    rs>4  我们可以知道要的答案肯定不可能出现在下一次快排的右边,一定出现在快排的左边,我们只需要在左边查找便是了

在左边如何查找??如果我们的快排是稳定的话(事实上是可以做到稳定的) 下一次查找的开始应该不包括【1,1】中在左边的数,也就是下一层的查询左界应该是( 1+【1,1】中被快排分到左边的个数),有界应该是 (左界+【2,8】被快排分到左边的个数 -1),放在上图就是在qs[1]中查找【2,5】中的第4小,

也就是在qs[1]的 [1,5]中查找【2,5】中的第4小

【2,5】被快排分到左边的个数是 3   3<4也就是说 我们要找的数不可能在左边,应该是在右边

而且应该是在右边找第 4-3 小的数 最小的数 右边查找的左界应该是 (mid+1 +【1,1】中被分到右边的个数),而 查询的右界是 (左界 + 【2,5】中被分到右边的个数-1)

也就是在qs[2] 的 [4,5] 区间内查找【5,5】内的最小数。。这个数 就是 5了吧,。。。

我们可以用一个 sum[d][i] 来存储在快排深度为d时 区间内前i位被快排分到左边的数的个数;可以用o(1)的时间算出下次查找的区间

总的时间复杂都市n*log(n)*log(n)

以下是划分树的代码 

 1 #include <iostream>

 2 #include <string.h>

 3 #include <algorithm>

 4 #include <cstdio>

 5 using namespace std;

 6 #define cl(a,b) memset(a,b,sizeof(a))

 7 #define MAXN 100005

 8 

 9 typedef struct mydata

10 {

11     int p,v,h;

12 }D;

13 

14 int cmpv(D aa,D bb)

15 {

16     if(aa.v==bb.v)return aa.p<bb.p;

17     return aa.v<bb.v;

18 }

19 

20 int cmpp(D aa , D bb)

21 {

22     return aa.p<bb.p;

23 }

24 

25 D dat[MAXN];

26 int da[MAXN];

27 int sum[20][MAXN];

28 int qso[20][MAXN],n,m;

29 

30 void build(int l,int r,int c)

31 {

32     if(l==r)return ;

33     int mid=(l+r)/2;

34     int i,cl=0,cr=0;

35     for(i=l;i<=r;i++)

36     {

37         if(qso[c][i]<=mid)

38         {

39             sum[c][i]=sum[c][i-1]+1;

40             qso[c+1][l+cl]=qso[c][i];

41             ++cl;

42         }

43         else

44         {

45             sum[c][i]=sum[c][i-1];

46             qso[c+1][mid+1+cr]=qso[c][i];

47             ++cr;

48         }

49         if(i==l)sum[c][i]-=sum[c][i-1];

50     }

51     build(l,mid,c+1);

52     build(mid+1,r,c+1);

53 }

54 

55 int query(int d,int l,int r,int ql,int qr,int k)

56 {

57     if(l==r)return qso[d][l];

58     int mid=(l+r)/2;

59     int sl=sum[d][ql-1];

60     if(l==ql)sl=0;

61     int sr=sum[d][qr];

62     int rs=sr-sl;

63     if(k<=rs)return query(d+1,l,mid,l+sl,l+sr-1,k);

64     return query(d+1,mid+1,r,mid+1+ql-l-sl,mid+1+qr-l-sr,k-rs);

65 }

66 

67 int main()

68 {

69     int tt;

70     cin>>tt;

71     while(tt--)

72     {

73         cl(sum,0);

74         cl(qso,0);

75         scanf("%d %d",&n,&m);

76         int i,j,k,l,r,x;

77         for(i=1;i<=n;i++){

78             scanf("%d",&dat[i].v);

79             dat[i].p=i;

80         }

81         sort(dat+1,dat+n+1,cmpv);

82         for(i=1;i<=n;i++)

83         {

84             dat[i].h=i;

85             da[i]=dat[i].v;

86         }

87         sort(dat+1,dat+1+n,cmpp);

88         for(i=1;i<=n;i++)qso[0][i]=dat[i].h;

89         build(1,n,0);

90         for(i=0;i<m;i++)

91         {

92             scanf("%d %d %d",&l,&r,&k);

93             int pos=query(0,1,n,l,r,k);

94             printf("%d\n",da[pos]);

95         }

96     }

97     return 0;

98 }

 

 

这道题除了用划分树来做之外 还可以用可持久化线段树来做 貌似还可以用归并树来做

其实归并树就是划分树倒过来。。。。。。。。。。。。。。。。。

可持久化线段树---------------------“被一小撮不怀好意的人起了一个奇怪的名字”--主席树 哈哈

主席树的原理是  每次更新的时候。不是修改值  而是插入值。。

主席树的其他建模。。。。。我不是很清楚 只是弄懂了此题的建模。。

对于这种玄幻的数据结构,,我感觉我解释不清楚,,我觉得给我比较鲜明的一个提示是。。。。。。。每次更新是新建一条从根节点到目标节点的路劲。。对于没有修改的节点就用原先的,需要修改的就新插入。而需要插入的节点数是log(n)级的

这种数据结构的空间复杂度和时间复杂度都是o(n*log(n)*log(n))的

talk is cheap  show you the code

 1 #include <iostream>

 2 #include <string.h>

 3 #include <algorithm>

 4 #include <cstdio>

 5 using namespace std;

 6 #define cl(a,b) memset(a,b,sizeof(a))

 7 #define MAXN 100005

 8 

 9 int sum[MAXN*20];

10 int ls[MAXN*20];

11 int rs[MAXN*20];

12 int num[MAXN],cn;

13 int has[MAXN],m,n,ch;

14 int root[MAXN];

15 

16 int phash(int l,int r,int x)

17 {

18     if(l==r)return l;

19     int mid=(l+r)/2;

20     if(has[mid]==x)return mid;

21     if(has[mid]>x)return phash(l,mid,x);

22     return phash(mid+1,r,x);

23 }

24 

25 void build(int l,int r,int &tx)

26 {

27     tx=++cn;

28     sum[tx]=0;

29     if(l==r)return ;

30     int mid=(l+r)/2;

31     build(l,mid,ls[tx]);

32     build(mid+1,r,rs[tx]);

33 }

34 

35 void inst(int pos,int l,int r,int last,int &tx)

36 {

37     tx=++cn;

38     sum[tx]=sum[last]+1;

39     ls[tx]=ls[last];

40     rs[tx]=rs[last];

41 //    cout<<l<<' '<<r<<endl;

42     if(l==r)return ;

43     int mid=(l+r)/2;

44     if(pos<=mid)inst(pos,l,mid,ls[last],ls[tx]);

45     else inst(pos,mid+1,r,rs[last],rs[tx]);

46 }

47 

48 int query(int l,int r,int k,int last,int tx)

49 {

50     if(l==r)return l;

51     int mid=(l+r)/2;

52     int rum=sum[ls[tx]]-sum[ls[last]];

53     if(rum>=k)return query(l,mid,k,ls[last],ls[tx]);

54     else return query(mid+1,r,k-rum,rs[last],rs[tx]);

55 }

56 

57 int main()

58 {

59     int tt;

60     cin>>tt;

61     while(tt--)

62     {

63         cl(ls,0);

64         cl(rs,0);

65         cl(sum,0);

66         scanf("%d %d",&n,&m);

67         int i,j,k,l,r,x;

68         for(i=1;i<=n;i++){

69             scanf("%d",&num[i]);

70             has[i]=num[i];

71         }

72         sort(has+1,has+1+n);

73         i=1;j=1;

74         while(i<=n&&j<=n)

75         {

76             if(has[i]!=has[j])has[++i]=has[j++];

77             else j++;

78         }

79         ch=i;

80         cn=0;

81  //       cout<<ch<<endl;

82         build(1,ch,root[0]);

83         for(i=1;i<=n;i++)

84         {

85             inst(phash(1,ch,num[i]),1,ch,root[i-1],root[i]);

86         }

87         for(i=0;i<m;i++)

88         {

89             scanf("%d %d %d",&l,&r,&x);

90             printf("%d\n",has[query(1,ch,x,root[l-1],root[r])]);

91         }

92     }

93     return 0;

94 }

 

再多BB 两句 。。。划分树的可以很方便的求出区间第k大 而归并树可以很方便的求出区间比k大的数个数。。。想到了一道cf 的题,,,

而主席树这种玄幻的东西,,两个都可以做,,

最后引用这种玄幻的东西的作者的一句话

..这个东西是当初我弱不会划分树的时候写出来替代的一个玩意..被一小撮别有用心的人取了很奇怪的名字想法是对原序列的每一个前缀[1..i]建立出一颗线段树维护值域上每个数的出现次数,然后发现这样的树是可以减的,然后就没有然后了

你可能感兴趣的:(HDU)