可持久化线段树(主席树)入门笔记

何为可持久化线段树

 
    我们通常建立的线段树是可以支持修改操作更新操作和查询操作的,却不支持保存你更改每个操作的历史版本。而可持久化线段树就是可以保存各个历史版本的线段树。

    最朴素的保存各个历史版本的线段树就是将每次更改后的线段树都保存下来,但是这样操作有个很严重的问题:太吃空间了。你n次修改就要保存n+1棵树,数据量一大,你所需要的空间就直线上升。

    那有没有什么好解决这个问题的方法呢?有没有可以节省空间的方法呢?这时很容易想到,当前修改的线段树,除了修改过的结点以外,其他的结点跟上一棵线段树是一模一样的。那我们就可以通过共用这些相同的节点,来达到节省空间的目的,这就是可持久化线段树。

建立一棵可持久化线段树

  既然是可持久化线段树,辣么对于最初的线段树的每个结点的数据结构我们不仅要保留线段树所包含区间端点l,r,值和标记val,tag(多个tag同样),还要增加左子树和右子树的指针lt,rt

  ACM一般采用数组模拟链表方式,而历史化版本由于除了普通update更新结点,也有可能pushdown或pushup才更新结点,所以一般新建的结点即使作废了,你也不好删除,所以经常是不删除多余的历史版本的。

  跟线段树一样都是那几个操作,init()建立,update()更新,query()查询,pushdown()向下更新和pushup()向上更新。还需要记录每次更改的根结点,用一个root[](rt[])数组来存各个历史版本的根结点。不同之处在于init()和update()以及pushdown()、pushup()作为更新的手段(init的话是最初当做一棵空树去更新),需要每次更新都创建一个和原位置相同的结点,然后将父节点相应的孩子指针指向该结点,然后再修改。我们可以在更新的时候巧妙使用&,例如update(segtree[i].lt,l,r,val) 而update的函数变量表为update(int &i,int l,int r,int val), 函数体开头就是segtree[++cnt]=segtree[i]; i=cnt;这样一个巧妙的操作,就顺便把父节点的指针也指向了该结点,也就能将可持久化线段树的写法变得和普通线段树相差无几。

  每次更新新结点能构成一个小的简单的二叉树。

  然后其他操作就跟普通线段树一样了。

  下面直接上题练手,看代码:

习题一

To the moon

Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)
Total Submission(s): 5016    Accepted Submission(s): 1129

Problem Description
Background
To The Moon is a independent game released in November 2011, it is a role-playing adventure game powered by RPG Maker.
The premise of To The Moon is based around a technology that allows us to permanently reconstruct the memory on dying man. In this problem, we'll give you a chance, to implement the logic behind the scene.

You‘ve been given N integers A[1], A[2],..., A[N]. On these integers, you need to implement the following operations:
1. C l r d: Adding a constant d for every {Ai | l <= i <= r}, and increase the time stamp by 1, this is the only operation that will cause the time stamp increase.
2. Q l r: Querying the current sum of {Ai | l <= i <= r}.
3. H l r t: Querying a history sum of {Ai | l <= i <= r} in time t.
4. B t: Back to time t. And once you decide return to a past, you can never be access to a forward edition anymore.
.. N, M ≤ 105, |A[i]| ≤ 109, 1 ≤ l ≤ r ≤ N, |d| ≤ 104 .. the system start from time 0, and the first modification is in time 1, t ≥ 0, and won't introduce you to a future state.
 
Input
n m
A1 A2 ... An
... (here following the m operations. )
 
Output
... (for each query, simply print the result. )
 
Sample Input
10 5 1 2 3 4 5 6 7 8 9 10 Q 4 4 Q 1 10 Q 2 4 C 3 6 3 Q 2 4 2 4 0 0 C 1 1 1 C 2 2 -1 Q 1 2 H 1 2 1
 
Sample Output
4 55 9 15 0 1
 
 
 
和普通线段树区间求和一样,就是需要记录历史版本以便返回,代码如下:
  1 #include<cstdio>
  2 #include<iostream>
  3 #include<cstring>
  4 #define clr(x) memset(x,0,sizeof(x))
  5 #define LL long long
  6 using namespace std;
  7 struct segtree
  8 {
  9     int lt,rt,begin,end;
 10     LL sum,tag;
 11 }ret[800010];
 12 int l,r,rt[100010],a[100010],cnt,now,n,m,ct;
 13 char op;
 14 void init(int &i,int lt,int rt);
 15 void update(int &i,int l,int r,int val);
 16 LL query(int i,int l,int r);
 17 int max(int a,int b)
 18 {
 19     return a>b?a:b;
 20  } 
 21 int min(int a,int b)
 22 {
 23     return a<b?a:b;
 24  } 
 25 int main()
 26 {
 27     bool b=0;
 28     while(scanf("%d%d",&n,&m)!=EOF)
 29     {
 30         if(b)
 31             printf("\n");
 32         else
 33             b=1;
 34         now=cnt=0;
 35         for(int i=1;i<=n;i++)
 36             scanf("%d",&a[i]);
 37         init(rt[now],1,n);
 38         for(int i=1;i<=m;i++)
 39         {
 40             scanf(" %c",&op);
 41             if(op=='C')
 42             {
 43                 now++;
 44                 scanf("%d%d%d",&l,&r,&ct);    
 45                 update(rt[now]=rt[now-1],l,r,ct);
 46             }
 47             if(op=='Q')
 48             {
 49                 scanf("%d%d",&l,&r);
 50                 printf("%lld\n",query(rt[now],l,r));
 51             }
 52             if(op=='H')
 53             {
 54                 scanf("%d%d%d",&l,&r,&ct);
 55                 printf("%lld\n",query(rt[ct],l,r));
 56             }
 57             if(op=='B')
 58             {
 59                 scanf("%d",&now);
 60                 cnt=rt[now+1]-1;
 61             }
 62         }
 63     }
 64     return 0;
 65 }
 66 void init(int &i,int lt,int rt)
 67 {
 68     ret[++cnt]=ret[i];
 69     i=cnt;
 70     ret[i].begin=lt;
 71     ret[i].end=rt;
 72     if(lt==rt)
 73     {
 74         ret[i].sum=a[lt];
 75         return;
 76     }
 77     int mid=(lt+rt)>>1;
 78     init(ret[i].lt,lt,mid);
 79     init(ret[i].rt,mid+1,rt);
 80     ret[i].sum=ret[ret[i].lt].sum+ret[ret[i].rt].sum;
 81     return ;
 82 }
 83 void update(int &i,int l,int r,int val)
 84 {
 85     ret[++cnt]=ret[i];
 86     i=cnt;
 87     ret[i].sum+=(min(r,ret[i].end)-max(l,ret[i].begin)+1)*val;
 88     if(ret[i].begin>=l && ret[i].end<=r)
 89     {
 90         if(ret[i].begin!=ret[i].end)
 91             ret[i].tag+=val;
 92         return ;
 93     }
 94     int mid=(ret[i].begin+ret[i].end)>>1;
 95     if(r<=mid)
 96         update(ret[i].lt,l,r,val);
 97     else  if(l>mid)
 98         update(ret[i].rt,l,r,val);
 99     else 
100     {
101         update(ret[i].lt,l,r,val);
102         update(ret[i].rt,l,r,val);        
103     }
104     
105 }
106 LL query(int i,int l,int r)
107 {
108     if(ret[i].begin>=l && ret[i].end<=r)
109         return ret[i].sum;
110     LL tmp=(min(r,ret[i].end)-max(l,ret[i].begin)+1)*ret[i].tag;
111     int mid=(ret[i].begin+ret[i].end)>>1;
112     if(r<=mid)
113         tmp+=query(ret[i].lt,l,r);
114     else  if(l>mid)
115         tmp+=query(ret[i].rt,l,r);
116     else 
117         tmp+=query(ret[i].lt,l,r)+query(ret[i].rt,l,r);    
118     return tmp;
119 }

 

 

 

    辣么这东西除了存储历史版本还能解决什么问题呢?  

    那就是!区间第k大问题!

区间第k大问题

  即求解一个区间内第k大的数的问题。该问题又分为分为带修改以及不带修改的区间第k大问题。

可持久化线段树求解不带修改的区间第k大问题

  对于该问题有人直接复制该区间sort解决,也有用ST去筛选去除的,这两种方法对于大量询问是十分慢的。这里介绍用可持久化线段树解决该问题。

  首先是查询前的准备工作。

  对于该问题,我们可以先复制待查询数组num,形成mirror数组a,利用sort()和unique()对该数组排序去重,然后依照这个数组建立一棵空的权值线段树,该权值线段树结点i的权值num为该区间l~r(排好序并去重从小到大的a的区间l~r)内所有数字的数量,起初为0.对于叶子结点i还要建立多一个val成员,来记录该点对应的数字a[ret[i].l]。其实这里这么做就是将a数组区间所有数离散化了。而每个结点存储的值num即为该区间的数字数量。

  然后将待查询数组的数字从头到尾一个一个插入权值线段树中。每次插入都对应一个新的root,所以第i次插入就有一个root[i]。root[i]对应从一开始到现在所有数,也就是[1,r]所有数形成的权值线段树。而更改结点就是将其num++。

  好了准备工作做完了,接下来怎么实现查询呢?

  你有没有发觉,我们这样建立的权值线段树是可以相减的!相减的结果(root[j]-root[i]的线段树)对应的就是j到i+1之间的所有操作形成的线段树!换句话说对于区间[l,r],他的权值线段树对应root[r]-root[l-1]的线段树!这棵线段树存储了[l,r]区间内所有的数并从小到大排列。因此最重要的问题:快速将[l,r]内的所有数排序这个问题,化为可持久化线段树后,相当于通过预处理解决了。接下来就有些类似二分查找的思想进行查找。查询到结点i(i非叶子结点)时,若root[r]-root[l-1]对应的线段树(以下省略,直接当做一棵线段树)的左子树的数字数量ret[ret[i].lc].num>=k,则说明第k大的数在左区间内,查询左区间(左孩子),反之在右区间,查询右区间。直到到达一个叶子结点,返回val即为该其区间第k大数。

  下面是两道模板题:

 

习题二

K-th Number

 

Description

You are working for Macrohard company in data structures department. After failing your previous task about key insertion you were asked to write a new data structure that would be able to return quickly k-th order statistics in the array segment. 
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 first line of the input file contains n --- the size of the array, and m --- the number of questions to answer (1 <= n <= 100 000, 1 <= m <= 5 000). 
The second line contains n different integer numbers not exceeding 109 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

For each question output the answer to it --- the k-th number in sorted a[i...j] segment.

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

This problem has huge input,so please use c-style input(scanf,printf),or you may got time limit exceed.
 
 
首先是桶分法的做法(效率十分低下,蹭着过去的,你打打和我不一样的代码试试看,根本过不去。这个分桶不一样结果还不同):
 1 #include<cstdio>
 2 #include<iostream>
 3 #include<cstring>
 4 #include<cmath>
 5 #include<vector>
 6 #include<algorithm>
 7 #define clr(x) memset(x,0,sizeof(x))
 8 using namespace std;
 9 vector<int> bucket[500];
10 int a[200010],num[200010];
11 int L[100010],R[100010],K[100010];
12 const int b=1000;
13 int main()
14 {
15     int n,m;
16     scanf("%d%d",&n,&m);
17     for(int i=1;i<=n;i++)
18     {
19         scanf("%d",&a[i]);
20         num[i]=a[i];
21         bucket[i/b].push_back(a[i]);
22     }
23     for(int i=1;i<=m;i++)
24     {
25         scanf("%d%d%d",&L[i],&R[i],&K[i]);
26     }
27     sort(num+1,num+n+1);
28 
29     for(int i=0;i<=n/b;i++)
30     {
31         sort(bucket[i].begin(),bucket[i].end());
32     }
33     for(int i=1;i<=m;i++)
34     {
35         int l=L[i];
36         int r=R[i];
37         int k=K[i];
38         int ld=0;
39         int rd=n;
40         while(rd-ld>1)
41         {
42             int mid=(ld+rd)/2;
43  //           printf("%d %d",ld,rd);
44 
45             int x=num[mid];
46             int lt=l;
47             int rt=r+1;
48             int sum=0;
49             while(lt<rt && lt%b!=0) if(a[lt++]<=x) sum++;
50  //           printf(" %d %d",sum,lt);
51             while(lt<rt && rt%b!=0) if(a[--rt]<=x) sum++;
52  //           printf(" %d %d",sum,rt);
53             for(int i=lt/b;i*b+b<=rt;i++)
54             {
55                 sum+=upper_bound(bucket[i].begin(),bucket[i].end(),x) -bucket[i].begin();
56   //          printf(" %d %d",sum,lt);
57 
58             }
59             if(sum<k)
60                 ld=mid;
61             else
62                 rd=mid;
63  //               printf(" %d %d\n",sum,x);
64         }
65         printf("%d\n",num[rd]);
66     }
67     return 0;
68 }

 

 

 主席树代码如下:

 1 #include<cstdio>
 2 #include<iostream>
 3 #include<cstring>
 4 #include<algorithm>
 5 #define clr(x) memset(x,0,sizeof(x))
 6 using namespace std;
 7 struct segtree
 8 {
 9     int lc,rc,l,r,num,val;
10 }ret[2000010];
11 void init(int &i,int l,int r);
12 int find(int num,int l,int r);
13 void update(int &i,int pos);
14 int query(int i,int j,int k);
15 int num[100010],a[100010],n,m,root[100010],rnu,cnt,now;
16 int main()
17 {
18     clr(root);
19     clr(a);
20     clr(ret);
21     int lt,rt,k;
22     while(scanf("%d%d",&n,&m)!=EOF)
23     {
24         for(int i=1;i<=n;i++)
25             scanf("%d",&num[i]);
26         memcpy(a,num,sizeof(num));
27         sort(a+1,a+(n+1));
28         rnu=unique(a+1,a+(n+1))-a;
29         rnu--;
30         cnt=now=0;
31 /**        for(int i=0;i<=n;i++)
32             printf("%d ",a[i]);
33         printf("\n%d\n",rnu);*/
34         init(root[0],1,rnu);
35         for(int i=1;i<=n;i++)
36         {
37             now++;
38             update(root[now]=root[now-1],find(num[i],1,rnu));
39         }
40         for(int i=1;i<=m;i++)
41         {
42             scanf("%d%d%d",&lt,&rt,&k);
43             printf("%d\n",query(root[lt-1],root[rt],k));
44         }
45     }
46     return 0;
47 } 
48 void init(int &i,int l,int r)
49 {
50     ret[++cnt]=ret[i];
51     i=cnt;
52     ret[i].l=l;
53     ret[i].r=r;
54     if(l==r)
55     {
56         ret[i].val=a[l];
57         return ; 
58     }
59     int mid=(l+r)>>1;
60     init(ret[i].lc,l,mid);
61     init(ret[i].rc,mid+1,r);
62     return ;
63 }
64 int find(int num,int l,int r)
65 {
66     int mid=(l+r)>>1;
67     if(a[mid]>num && mid>l)
68         return find(num,l,mid-1);
69     if(a[mid]<num && mid<r)
70         return find(num,mid+1,r);
71     return mid;
72 }
73 void update(int &i,int pos)
74 {
75     ret[++cnt]=ret[i];
76     i=cnt;
77     ret[i].num++;
78     int mid=(ret[i].l+ret[i].r)>>1;
79     if(mid>=pos && ret[i].l!=ret[i].r)
80         update(ret[i].lc,pos);
81     if(mid<pos &&  ret[i].l!=ret[i].r)
82         update(ret[i].rc,pos);
83     return ;
84 }
85 int query(int i,int j,int k)
86 {
87     if(ret[i].l==ret[i].r)
88         return ret[i].val;
89     int size=ret[ret[j].lc].num-ret[ret[i].lc].num;
90     if(size>=k)
91         return query(ret[i].lc,ret[j].lc,k);
92     else
93         return query(ret[i].rc,ret[j].rc,k-size);
94 }

 

 

  后来想了想,离散化后不需要定位,直接把该数字插入就好了,可以省去find()函数(或lower_bound()),相应的你将每个结点的区间[l,r]右端点所对应的a数组的值作为其val值,更新查找只需要和该val比较就行。

  优化后又做了道模板题,如下:

习题三

 

Super Mario

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 6137    Accepted Submission(s): 2667


Problem Description

 

Mario is world-famous plumber. His “burly” figure and amazing jumping ability reminded in our memory. Now the poor princess is in trouble again and Mario needs to save his lover. We regard the road to the boss’s castle as a line (the length is n), on every integer point i there is a brick on height hi. Now the question is how many bricks in [L, R] Mario can hit if the maximal height he can jump is H.

 

 


Input

 

The first line follows an integer T, the number of test data.
For each test data:
The first line contains two integers n, m (1 <= n <=10^5, 1 <= m <= 10^5), n is the length of the road, m is the number of queries.
Next line contains n integers, the height of each brick, the range is [0, 1000000000].
Next m lines, each line contains three integers L, R,H.( 0 <= L <= R < n 0 <= H <= 1000000000.)

 

 


Output

 

For each case, output "Case X: " (X is the case number starting from 1) followed by m lines, each line contains an integer. The ith integer is the number of bricks Mario can hit for the ith query.

 

 


Sample Input

 

1 10 10 0 5 2 7 5 4 3 8 7 7 2 8 6 3 5 0 1 3 1 1 9 4 0 1 0 3 5 5 5 5 1 4 6 3 1 5 7 5 7 3

 

 


Sample Output

 

Case 1: 4 0 0 3 1 2 0 1 5 1

 

 
  题意差不多就是给你个区间[l,r],再给你个k,要你查询该区间小于k的数有多少个。虽然不是第k大,但仍需要可持久化线段树对于区间的易操作性质。
  还有要注意,区间是从0开始的,即初始区间为[0,n-1]。然后,数据量没那么大,不然ret要开170w的但是100w就解决了。但假如真到上限170w按照我的结点的struct会爆存储量。因此应该把左右区间的运算放到函数中去,释放struct中的l,r占的存储空间,空间能下降一半。
 1 #include<cstdio>
 2 #include<iostream>
 3 #include<cstring>
 4 #include<algorithm>
 5 #define clr(x) memset(x,0,sizeof(x))
 6 using namespace std;
 7 struct segtree
 8 {
 9     int l,r,val,num,lc,rc;
10 }ret[1000010];
11 int a[100010],num[100010],n,m,now,root[100010],cnt,rnu,l,r,k;
12 void init(int &i,int l,int r);
13 void update(int &i,int num);
14 int query(int i,int j,int k);
15 int main()
16 {
17     int T;
18     scanf("%d",&T);
19     clr(ret);
20     for(int tt=1;tt<=T;tt++)
21     {
22         printf("Case %d:\n",tt);
23         scanf("%d%d",&n,&m);
24         for(int i=1;i<=n;i++)
25             scanf("%d",&num[i]);
26         memcpy(a,num,sizeof(num));
27         sort(a+1,a+(n+1));
28         rnu=unique(a+1,a+(n+1))-a;
29         rnu--;
30         cnt=now=0;
31         init(root[0],1,rnu);
32         for(int i=1;i<=n;i++)
33         {
34             now++;
35             update(root[now]=root[now-1],num[i]);
36         }
37         for(int i=1;i<=m;i++)
38         {
39             scanf("%d%d%d",&l,&r,&k);
40             printf("%d\n",query(root[l],root[r+1],k));
41         }
42     }
43     return 0;
44 }
45 void init(int &i,int l,int r)
46 {
47     ret[++cnt]=ret[i];
48     i=cnt;
49     ret[i].l=l;
50     ret[i].r=r;
51     int mid=(l+r)>>1;
52     ret[i].val=a[r];
53     if(l==r)
54     {
55         return ;
56     }
57     init(ret[i].lc,l,mid);
58     init(ret[i].rc,mid+1,r);
59     return ;
60 }
61 void update(int &i,int num)
62 {
63     ret[++cnt]=ret[i];
64     i=cnt;
65     ret[i].num++;
66     if(ret[i].l==ret[i].r)
67         return;
68     int mid=(ret[i].l+ret[i].r)>>1;
69     if(a[mid]>=num)
70         update(ret[i].lc,num);
71     else
72         update(ret[i].rc,num);
73     return ;
74 }
75 int query(int i,int j,int k)
76 {
77     if(ret[i].val<=k)
78         return ret[j].num-ret[i].num;
79     else
80     {
81         int mid=(ret[i].l+ret[i].r)>>1;
82         if(a[mid+1]<=k)
83             return query(ret[i].lc,ret[j].lc,k)+query(ret[i].rc,ret[j].rc,k);
84         else
85             return query(ret[i].lc,ret[j].lc,k);
86     }
87 }

树套树求解带修改的区间第k大问题

  做这类题,我们需要一个树套树板子,或者手打一个板子(如果你够强时间够多)。常见做法有线段树套平衡树,树状数组套可持久化线段树,整体二分。在这里我们讲树状数组套可持久化线段树。

  我们先来看看不套树的情况。我们若是对某个点l进行修改,那么版本l及之后的版本都需要修改,这样增加的修改时间是O(n), 显然很多题目这样的时间试过不了的。

  我们发觉,对于一个历史版本l,他更倾向于一个从1到l的单个结点树的前缀和,这样我们就可以考虑将历史版本再细分,以树状数组的形式来对该历史版本求和,当然存入的时候也以树状数组的形式存入。

  我们还需要一个root数组,这时候root数组表示的意义,就跟树状数组里一样了,他表示的是对应历史版本区间[l,r]的结点树构成的一棵线段树。这样我们在存入的时候虽然要*O(logn)的时间以树状数组的形式存入,但是我们却可以在O(logn)的时间内修改点值,而查询的时间较之前要*O(logn),总体上来说对于多修改的第k大能尽量缩少时间。

  因为是历史化版本,所以废弃的空间我们是无法收回的,约会浪费和原来线段树相同结点数的空间。

下面两道例题,第一道,这样的树套树效率还是显得低下,可以考虑用其他方法来做,把代码贴出来只是为了安慰自己的第一个树套树代码。。也能算模板吧。。第二个树套树能过,加油做吧~

 习题四

 

 

CRB and Queries

Time Limit: 12000/6000 MS (Java/Others)    Memory Limit: 131072/131072 K (Java/Others)
Total Submission(s): 3249    Accepted Submission(s): 834


Problem Description

 

There are N boys in CodeLand.
Boy i has his coding skill Ai.
CRB wants to know who has the suitable coding skill.
So you should treat the following two types of queries.
Query 1: 1 l v
The coding skill of Boy l has changed to v.
Query 2: 2 l r k
This is a report query which asks the k-th smallest value of coding skill between Boy l and Boy r(both inclusive).

 

 


Input

 

There are multiple test cases. 
The first line contains a single integer N.
Next line contains N space separated integers A1A2, …, AN, where Ai denotes initial coding skill of Boy i.
Next line contains a single integer Q representing the number of queries.
Next Q lines contain queries which can be any of the two types.
1 ≤ NQ ≤ 105
1 ≤ Aiv ≤ 109
1 ≤ l ≤ r ≤ N
1 ≤ k ≤ r  l + 1

 

 


Output

 

For each query of type 2, output a single integer corresponding to the answer in a single line.

 

 


Sample Input

 

5 1 2 3 4 5 3 2 2 4 2 1 3 6 2 2 4 2

 

 


Sample Output

 

3 4

 

 

 

 树状数组套可持久化线段树代码:

  1 #include<cstdio>
  2 #include<iostream>
  3 #include<cstring>
  4 #include<algorithm>
  5 #define clr(x) memset(x,0,sizeof(x))
  6 using namespace std;
  7 struct segtree
  8 {
  9     int l,r,val,num,lc,rc;
 10 }ret[1000010];//可持久化权值线段树,val为右端点对应的数字
 11 struct node
 12 {
 13     int tmp,l,r,k,pos,v;
 14 }que[100010];//操作数组,存储所有m个操作,该题目离线处理
 15 int a[200010],num[100010],cnt,root[100010],n,m,l,r,k,tr[100010],rnu,op;//a为所有出现的数字离散化后的排序数组,num为初始各个位置的数组,root为每个树状数组代表区间对应的数字的根节点。tr为并行处理求[l,r]和时需要用到的数组
 16 void init(int &i,int l,int r);
 17 void add(int i,int num,int x); 
 18 void update(int &i,int num,int val);
 19 int sum(int i,int j,int k);
 20 int query(int i,int j,int k);
 21 int getsum(int l);
 22 int main()
 23 {
 24     while(scanf("%d",&n)!=EOF)
 25     {
 26         //初始化 
 27         clr(ret);
 28         clr(tr);
 29         clr(que);
 30         clr(root);
 31         for(int i=1;i<=n;i++)
 32         {
 33             scanf("%d",&num[i]);
 34         }
 35         memcpy(a,num,sizeof(num));//复制a数组作为离散化数组
 36         cnt=0;
 37         rnu=n;
 38         scanf("%d",&m);
 39         //将所有操作读入以后离线处理,很多都是这么做的 
 40         for(int i=1;i<=m;i++)
 41         {
 42             scanf("%d",&op);
 43             if(op==1)
 44             {
 45                 que[i].tmp=1;
 46                 scanf("%d%d",&que[i].pos,&que[i].v);
 47                 a[++rnu]=que[i].v; //将有可能出现的数字加入a数组中 
 48             }
 49             else
 50             {
 51                 que[i].tmp=2;
 52                 scanf("%d%d%d",&que[i].l,&que[i].r,&que[i].k);
 53             }
 54         }
 55         //a排序去重 
 56         sort(a+1,a+rnu+1);
 57         rnu=unique(a+1,a+rnu+1)-a-1;
 58 //       printf("-1\n");
 59         init(root[0],1,rnu);//建初始空树 
 60         for(int i=1;i<=n;i++)
 61             root[i]=root[0];//每个区间最初的状态,相当于一维树状数组的0初状态 
 62 //        printf("1\n");
 63         for(int i=1;i<=n;i++)
 64             add(i,num[i],1);//以树状数组分割的形方式加入每个数字 
 65 //        printf("2\n");
 66         for(int i=1;i<=m;i++)
 67             if(que[i].tmp==1)
 68             {
 69                 add(que[i].pos,num[que[i].pos],-1);//删除原来的数字 
 70                 add(que[i].pos,que[i].v,1);//加入修改后的数字 
 71                 num[que[i].pos]=que[i].v;
 72             }
 73             else
 74             {
 75                 printf("%d\n",sum(que[i].l-1,que[i].r,que[i].k));//输出第K大数字 
 76             }
 77     }
 78     return 0;
 79 }
 80 void init(int &i,int l,int r)
 81 {
 82     ret[++cnt]=ret[i];
 83     i=cnt;
 84 //    printf("%d %d\n",l,r);
 85     ret[i].l=l;
 86     ret[i].r=r;
 87     ret[i].val=a[r];
 88     if(l==r)
 89         return ;
 90     int mid=(l+r)>>1;
 91     init(ret[i].lc,l,mid);
 92     init(ret[i].rc,mid+1,r);
 93     return ;
 94 }
 95 void add(int i,int num,int x)
 96 {
 97 
 98     for(;i<=n;i+=i&-i) 
 99         update(root[i],num,x);//每个需要的版本加入该元素 
100     return ;
101 }
102 void update(int &i,int num,int val)
103 {
104     ret[++cnt]=ret[i];
105     i=cnt;
106   //      printf("%d %d %d %d\n",ret[i].l,ret[i].r,ret[i].val,ret[i].num);
107     ret[i].num+=val;
108     if(ret[i].l==ret[i].r)
109         return ;
110     if(num<=ret[ret[i].lc].val)
111         update(ret[i].lc,num,val);
112     else
113         update(ret[i].rc,num,val);
114     return ;
115 }
116 int sum(int i,int j,int k)
117 {
118         //先将需用到的区间的标号的根结点更新至tr数组 
119         for(int l=i;l>0;l-=l&-l)
120             tr[l]=root[l];
121         for(int r=j;r>0;r-=r&-r)
122             tr[r]=root[r];
123 //        printf("k:%d\n",k);
124         return a[query(i,j,k)];//查询得出位置,返回该位置数字 
125 }
126 int query(int i,int j,int k)
127 {
128     if(ret[tr[i]].l==ret[tr[i]].r)
129         return ret[tr[i]].l;
130     int lt=ret[tr[i]].l,rt=ret[tr[i]].r;
131 //多棵树并行走用getsum求某个前缀和线段树的和,相当于求玩前缀和后的然后右版本j-左版本i,跟两颗时是一样的 
132     int sum=getsum(j)-getsum(i);
133 //    printf("sum:%d %d %d %d\n",sum,k,ret[tr[i]].l,ret[tr[i]].r);
134     if(sum>=k)
135     {
136         //所有树往左孩子走 
137         for(int l=i;l>0;l-=l&-l)
138             tr[l]=ret[tr[l]].lc;
139         for(int r=j;r>0;r-=r&-r)
140             if(ret[tr[r]].l==lt && ret[tr[r]].r==rt)//防止多次对一个区间偏移 
141                 tr[r]=ret[tr[r]].lc;
142         return query(i,j,k);
143     }
144     else
145     {
146         //所有树往左孩子走 
147         for(int l=i;l>0;l-=l&-l)
148             tr[l]=ret[tr[l]].rc;
149         for(int r=j;r>0;r-=r&-r)
150             if(ret[tr[r]].l==lt && ret[tr[r]].r==rt)
151                 tr[r]=ret[tr[r]].rc;
152         return query(i,j,k-sum);
153     }
154 }
155 int getsum(int l)//求多棵树左孩子的权值。 
156 {
157     int ans=0;
158     for(int i=l;i>0;i-=i&-i) ans+=ret[ret[tr[i]].lc].num;
159     return ans;
160 }

 

 

 

下面是一些练习

hdu 3727

Jewel

Problem Description
Jimmy wants to make a special necklace for his girlfriend. He bought many beads with various sizes, and no two beads are with the same size. Jimmy can't remember all the details about the beads, for the necklace is so long. So he turns to you for help.

Initially, there is no bead at all, that is, there is an empty chain. Jimmy always sticks the new bead to the right of the chain, to make the chain longer and longer. We number the leftmost bead as Position 1, and the bead to its right as Position 2, and so on. Jimmy usually asks questions about the beads' positions, size ranks and actual sizes. Specifically speaking, there are 4 kinds of operations you should process:

Insert x
Put a bead with size x to the right of the chain (0 < x < 231, and x is different from all the sizes of beads currently in the chain)
Query_1 s t k
Query the k-th smallest bead between position s and t, inclusive. You can assume 1 <= s <= t <= L, (L is the length of the current chain), and 1 <= k <= min (100, t-s+1)
Query_2 x
Query the rank of the bead with size x, if we sort all the current beads by ascent order of sizes. The result should between 1 and L (L is the length of the current chain)
Query_3 k
Query the size of the k-th smallest bead currently (1 <= k <= L, L is the length of the current chain)
 
 
这题mdzz害我看了半天,网上看了别人开的结构体大小才发觉! 这个insert最多有20w,呵呵了。。还有x<2^31别被坑了。最后因为答案是累加所以最好LL。然后,你要卡空间0 0。
  1 #include<cstdio>
  2 #include<iostream>
  3 #include<cstring>
  4 #include<algorithm>
  5 #define LL long long
  6 #define clr(x) memset(x,0,sizeof(x))
  7 using namespace std;
  8 struct segtree
  9 {
 10     int num,lc,rc;
 11 }ret[4000010];
 12 struct ques
 13 {
 14     int tmp,l,r,k,x;
 15 }que[200010];
 16 char s[100];
 17 int root[200010],a[200010],rnu,cnt,l,r,k,pos,now,n,m;
 18 int BIT[200010];
 19 LL ans1,ans2,ans3;
 20 void add(int i)
 21 {
 22     for(int x=i;x<=rnu;x+=x&-x) BIT[x]++;
 23     return ;
 24 }
 25 int sum(int i)
 26 {
 27     int s;
 28     for(int x=i;x>0;x-=x&-x) s+=BIT[x];
 29     return s;
 30 }
 31 void init(int &i,int lt,int rt);
 32 void update(int &i,int num,int lt,int rt);
 33 int query(int i,int j,int lt,int rt,int k);
 34 int Query(int i,int j,int lt,int rt,int num);
 35 int main()
 36 {
 37     int tt=0;
 38     while(scanf("%d",&n)!=EOF)
 39     {
 40         printf("Case %d:\n",++tt);
 41         ret[0].num=ret[0].lc=ret[0].rc=0;
 42         root[0]=0;
 43         clr(BIT);
 44         rnu=now=cnt=0;
 45         ans1=ans2=ans3=0;
 46         for(int i=1;i<=n;i++)
 47         {
 48             scanf("%s",s);
 49             if(s[0]=='I')
 50             {
 51                 que[i].tmp=0;
 52                 scanf("%d",&que[i].x);
 53                 a[++rnu]=que[i].x;
 54             }
 55             else
 56             {
 57                 if(s[strlen(s)-1]=='1')
 58                 {
 59                     que[i].tmp=1;
 60                     scanf("%d%d%d",&que[i].l,&que[i].r,&que[i].k);
 61                 }
 62                 if(s[strlen(s)-1]=='2')
 63                 {
 64                     que[i].tmp=2;
 65                     scanf("%d",&que[i].x);
 66                 }
 67                 if(s[strlen(s)-1]=='3')
 68                 {
 69                     que[i].tmp=3;
 70                     scanf("%d",&que[i].k);
 71                 }
 72             }
 73         }
 74         sort(a+1,a+rnu+1);
 75         rnu=unique(a+1,a+rnu+1)-a-1;
 76         init(root[0],1,rnu);
 77         for(int i=1;i<=n;i++)
 78         {
 79             if(que[i].tmp==0)
 80             {
 81                 now++;
 82                 update(root[now]=root[now-1],que[i].x,1,rnu);
 83                 pos=lower_bound(a+1,a+rnu+1,que[i].x)-a;
 84                 add(pos);
 85             }
 86             if(que[i].tmp==1)
 87             {
 88                 ans1+=1LL*query(root[que[i].l-1],root[que[i].r],1,rnu,que[i].k);
 89             }
 90             if(que[i].tmp==2)
 91             {
 92                 pos=lower_bound(a+1,a+rnu+1,que[i].x)-a;
 93                 ans2+=1LL*sum(pos);
 94             }
 95             if(que[i].tmp==3)
 96             {
 97                 ans3+=1LL*query(root[0],root[now],1,rnu,que[i].k);
 98             }
 99         }
100         printf("%lld\n%lld\n%lld\n",ans1,ans2,ans3);
101     }
102     return 0;
103 }
104 void init(int &i,int lt,int rt)
105 {
106     ret[++cnt]=ret[i];
107     i=cnt;
108     if(lt==rt)
109         return;
110     int mid=(lt+rt)>>1;
111     init(ret[i].lc,lt,mid);
112     init(ret[i].rc,mid+1,rt);
113     return ;
114 }
115 void update(int &i,int num,int lt,int rt)
116 {
117     ret[++cnt]=ret[i];
118     i=cnt;
119     ret[i].num++;
120     if(lt==rt) return ;
121     int mid=(lt+rt)>>1;
122     if(num<=a[mid])
123         update(ret[i].lc,num,lt,mid);
124     else
125         update(ret[i].rc,num,mid+1,rt);
126     return ;
127 }
128 int query(int i,int j,int lt,int rt,int k)
129 {
130     int mid=(lt+rt)>>1;
131     if(lt==rt)
132         return a[rt];
133     if(ret[ret[j].lc].num-ret[ret[i].lc].num>=k)
134         return query(ret[i].lc,ret[j].lc,lt,mid,k);
135     else
136         return query(ret[i].rc,ret[j].rc,mid+1,rt,k-(ret[ret[j].lc].num-ret[ret[i].lc].num));
137 }
极简BIT+可持久化线段树
  1 #include<cstdio>
  2 #include<iostream>
  3 #include<cstring>
  4 #include<algorithm>
  5 #define LL long long
  6 #define clr(x) memset(x,0,sizeof(x))
  7 using namespace std;
  8 struct segtree
  9 {
 10     int num,lc,rc;
 11 }ret[4000010];
 12 struct ques
 13 {
 14     int tmp,l,r,k,x;
 15 }que[200010];
 16 char s[100];
 17 int root[200010],a[200010],rnu,cnt,l,r,k,pos,now,n,m;
 18 LL ans1,ans2,ans3;
 19 int Query(int i,int j,int lt,int rt,int num);
 20 void init(int &i,int lt,int rt);
 21 void update(int &i,int num,int lt,int rt);
 22 int query(int i,int j,int lt,int rt,int k);
 23 int main()
 24 {
 25     int tt=0;
 26     while(scanf("%d",&n)!=EOF)
 27     {
 28         printf("Case %d:\n",++tt);
 29         ret[0].num=ret[0].lc=ret[0].rc=0;
 30         root[0]=0;
 31         rnu=now=cnt=0;
 32         ans1=ans2=ans3=0;
 33         for(int i=1;i<=n;i++)
 34         {
 35             scanf("%s",s);
 36  //           printf("%s ",s);
 37             if(s[0]=='I')
 38             {
 39                 que[i].tmp=0;
 40                 scanf("%d",&que[i].x);
 41                 a[++rnu]=que[i].x;
 42  //               printf("%d\n",que[i].x);
 43             }
 44             else
 45             {
 46                 if(s[strlen(s)-1]=='1')
 47                 {
 48                     que[i].tmp=1;
 49                     scanf("%d%d%d",&que[i].l,&que[i].r,&que[i].k);
 50 //                    printf("%d %d %d\n",que[i].l,que[i].r,que[i].k);
 51                 }
 52                 if(s[strlen(s)-1]=='2')
 53                 {
 54                     que[i].tmp=2;
 55                     scanf("%d",&que[i].x);
 56 //                    printf("%d\n",que[i].x);
 57                 }
 58                 if(s[strlen(s)-1]=='3')
 59                 {
 60                     que[i].tmp=3;
 61                     scanf("%d",&que[i].k);
 62 //                    printf("%d\n",que[i].k);
 63                 }
 64             }
 65         }
 66 //      printf("-1\n");
 67         sort(a+1,a+rnu+1);
 68         rnu=unique(a+1,a+rnu+1)-a-1;
 69         init(root[0],1,rnu);
 70         for(int i=1;i<=n;i++)
 71         {
 72             if(que[i].tmp==0)
 73             {
 74                 now++;
 75                 update(root[now]=root[now-1],que[i].x,1,rnu);
 76             }
 77             if(que[i].tmp==1)
 78             {
 79                 ans1+=1LL*query(root[que[i].l-1],root[que[i].r],1,rnu,que[i].k);
 80             }
 81             if(que[i].tmp==2)
 82             {
 83                 ans2+=1LL*Query(root[0],root[now],1,rnu,que[i].x);
 84             }
 85             if(que[i].tmp==3)
 86             {
 87                 ans3+=1LL*query(root[0],root[now],1,rnu,que[i].k);
 88             }
 89         }
 90         printf("%lld\n%lld\n%lld\n",ans1,ans2,ans3);
 91     }
 92     return 0;
 93 }
 94 void init(int &i,int lt,int rt)
 95 {
 96     ret[++cnt]=ret[i];
 97     i=cnt;
 98     if(lt==rt)
 99         return;
100     int mid=(lt+rt)>>1;
101     init(ret[i].lc,lt,mid);
102     init(ret[i].rc,mid+1,rt);
103     return ;
104 }
105 void update(int &i,int num,int lt,int rt)
106 {
107     ret[++cnt]=ret[i];
108     i=cnt;
109     ret[i].num++;
110     if(lt==rt) return ;
111     int mid=(lt+rt)>>1;
112     if(num<=a[mid])
113         update(ret[i].lc,num,lt,mid);
114     else
115         update(ret[i].rc,num,mid+1,rt);
116     return ;
117 }
118 int query(int i,int j,int lt,int rt,int k)
119 {
120     int mid=(lt+rt)>>1;
121     if(lt==rt)
122         return a[rt];
123     if(ret[ret[j].lc].num-ret[ret[i].lc].num>=k)
124         return query(ret[i].lc,ret[j].lc,lt,mid,k);
125     else
126         return query(ret[i].rc,ret[j].rc,mid+1,rt,k-(ret[ret[j].lc].num-ret[ret[i].lc].num));
127 }
128 
129 int Query(int i,int j,int lt,int rt,int num)
130 {
131     if(a[rt]<=num)
132         return ret[j].num-ret[i].num;
133     else
134     {
135         int mid=(lt+rt)>>1;
136         if(a[mid]>=num)
137             return Query(ret[i].lc,ret[j].lc,lt,mid,num);
138         else
139             return Query(ret[i].lc,ret[j].lc,lt,mid,num)+Query(ret[i].rc,ret[j].rc,mid+1,rt,num);
140     }
141 }
可持久化线段树

 hdu 4605

Magic Ball Game

Problem Description
When the magic ball game turns up, Kimi immediately falls in it. The interesting game is made up of N balls, each with a weight of w[i]. These N balls form a rooted tree, with the 1st ball as the root. Any ball in the game has either 0 or 2 children ball. If a node has 2 children balls, we may define one as the left child and the other as the right child.
The rules are simple: when Kimi decides to drop a magic ball with a weight of X, the ball goes down through the tree from the root. When the magic ball arrives at a node in the tree, there's a possibility to be catched and stop rolling, or continue to roll down left or right. The game ends when the ball stops, and the final score of the game depends on the node at which it stops.
After a long-time playing, Kimi now find out the key of the game. When the magic ball arrives at node u weighting w[u], it follows the laws below:
1  If X=w[u] or node u has no children balls, the magic ball stops.
2  If X<w[u], there's a possibility of 1/2 for the magic ball to roll down either left or right.
3  If X>w[u], the magic ball will roll down to its left child in a possibility of 1/8, while the possibility of rolling down right is 7/8.
In order to choose the right magic ball and achieve the goal, Kimi wonders what's the possibility for a magic ball with a weight of X to go past node v. No matter how the magic ball rolls down, it counts if node v exists on the path that the magic ball goes along.
Manual calculating is fun, but programmers have their ways to reach the answer. Now given the tree in the game and all Kimi's queries, you're required to answer the possibility he wonders.
 
 
 

题意:

  有一颗树,根节点为1,每一个节点要么有两个子节点,要么没有,每个节点都有一个权值wi 。然后,有一个球,附带值x 。

  球到达某个节点上,如果x==wi,那么球停在这个节点上 。当然,这个点是叶子节点也会停止 。

  如果x<wi,那么有1/2的概率走向左子树,有1/2的概率走向右子树 。

  如果x>wi,那么有1/8的概率走向左子树,有7/8的概率走向右子树 。

  问球经过v节点的概率 。(停在v节点也算)

 
 
  我们先建立一棵树,结点记录它的父结点fa[],权值w[],为父结点的左右子树dir[]。
  然后我们可以知道从根结点到达某个结点的路径是唯一的。然后我们可以将题目转化为求根root[1]到点v小于x的往左走的结点数以及往右走的结点数,总结点数。这样就转化为根到v的所有结点第k大问题,用可持久化线段树就能在线询问的时候做。我们对每个结点都建立两个根,往左走结点数的根rootl[]及往右走的结点数的根rootr[]。初版本为rootl[1]及rootr[1],皆为空树。每个版本i的上一个版本为fa[i],因此就能逐一将各个结点的主席树建好。然后对每次询问,只需要从v版本中找到相应的小于x的往左往右走的结点数,稍加计算就能得出答案。另:由于存在若该版本中存在x则不可能到达,所以需要查询该树中有无x,我的方法是query()查询小于等于x所有结点数以及小于等于x-1的所有结点数是否相等来判断。
在线可持久化做法:
  1 #include<cstdio>
  2 #include<iostream>
  3 #include<cstring>
  4 #include<algorithm>
  5 #include<string>
  6 #define clr(x) memset(x,0,sizeof(x))
  7 using namespace std;
  8 struct segtree
  9 {
 10     int l,r,val,lc,rc,num;
 11 }ret[2000010];
 12 int fa[100010],w[100010],a[100010],rootl[100010],rootr[100010],n,m,l,r,k,rnu,cnt,now,q,v,x,ansl,ansr,pow2,pow7;
 13 bool dir[100010];
 14 void init(int &i,int l,int r);
 15 void build(int i);
 16 void update(int &i,int num);
 17 int query(int i,int x);
 18 int main()
 19 {
 20     int T;
 21     scanf("%d",&T);
 22     ret[0].l=ret[0].r=ret[0].val=ret[0].lc=ret[0].rc=ret[0].num=0;
 23     while(T--)
 24     {
 25         scanf("%d",&n);
 26         for(int i=1;i<=n;i++)
 27         {
 28             scanf("%d",&w[i]);
 29             fa[i]=i;
 30         }
 31         cnt=now=0;
 32         clr(rootl);
 33         clr(rootr);
 34         memcpy(a,w,sizeof(w));
 35         sort(a+1,a+n+1);
 36         rnu=unique(a+1,a+n+1)-a-1;
 37         scanf("%d",&m);
 38         for(int i=1;i<=m;i++)
 39         {
 40             scanf("%d%d%d",&k,&l,&r);
 41             fa[l]=k;
 42             fa[r]=k;
 43             dir[l]=0;
 44             dir[r]=1;
 45         }
 46         init(rootl[1],1,rnu);
 47         init(rootr[1],1,rnu);
 48         for(int i=2;i<=n;i++)
 49             if(!rootl[i])
 50                 build(i);
 51         scanf("%d",&q);
 52         for(int i=1;i<=q;i++)
 53         {
 54             scanf("%d%d",&v,&x);
 55  //               printf("%d %d\n",query(rootl[v],x-1),query(rootr[v],x-1));
 56             if((ansl=query(rootl[v],x))+(ansr=query(rootr[v],x))!=query(rootl[v],x-1)+query(rootr[v],x-1))
 57             {
 58                 printf("0\n");
 59                 continue;
 60             }
 61             else
 62             {
 63 //              printf("%d %d\n",ansl,ansr);
 64                 pow2=ansl*3+ansr*3+ret[rootl[v]].num-ansl+ret[rootr[v]].num-ansr;
 65                 pow7=ansr;
 66                 printf("%d %d\n",pow7,pow2);
 67             }
 68         }
 69     }
 70     return 0;
 71 }
 72 void init(int &i,int l,int r)
 73 {
 74     ret[++cnt]=ret[i];
 75     i=cnt;
 76     ret[i].l=l;
 77     ret[i].r=r;
 78     ret[i].val=a[r];
 79     if(l==r)
 80         return ;
 81     int mid=(l+r)>>1;
 82     init(ret[i].lc,l,mid);
 83     init(ret[i].rc,mid+1,r);
 84     return ;
 85 }
 86 void build(int i)
 87 {
 88     if(!rootl[fa[i]])
 89         build(fa[i]);
 90  //   printf("%d %d\n",i,fa[i]);
 91     if(!dir[i])
 92     {
 93         rootr[i]=rootr[fa[i]];
 94         update(rootl[i]=rootl[fa[i]],w[fa[i]]);
 95     }
 96     else
 97     {
 98         rootl[i]=rootl[fa[i]];
 99         update(rootr[i]=rootr[fa[i]],w[fa[i]]);
100     }
101     return ;
102 }
103 void update(int &i,int num)
104 {
105     ret[++cnt]=ret[i];
106     i=cnt;
107  //   printf("%d %d %d %d\n",ret[i].l,ret[i].r,ret[i].val,ret[i].num);
108     ret[i].num++;
109     if(ret[i].l==ret[i].r)
110         return ;
111     if(num<=ret[ret[i].lc].val)
112         update(ret[i].lc,num);
113     else
114         update(ret[i].rc,num);
115     return ;
116 }
117 int query(int i,int x)
118 {
119     if(ret[i].val<=x)
120         return ret[i].num;
121     int mid=(ret[i].l+ret[i].r)>>1;
122     if(a[mid+1]<=x)
123         return query(ret[i].lc,x)+query(ret[i].rc,x);
124     else
125         return query(ret[i].lc,x);
126 }
可持久化线段树

 离线可用BIT做,想法差不多,需要将查询的结点按dfs排个序。然后瞎搞搞。这里就不再给出代码了。

 SPOJ COT

COT - Count on a tree

 

You are given a tree with N nodes.The tree nodes are numbered from 1 to N.Each node has an integer weight.

We will ask you to perform the following operation:

  • u v k : ask for the kth minimum weight on the path from node u to node v

 

Input

In the first line there are two integers N and M.(N,M<=100000)

In the second line there are N integers.The ith integer denotes the weight of the ith node.

In the next N-1 lines,each line contains two integers u v,which describes an edge (u,v).

In the next M lines,each line contains three integers u v k,which means an operation asking for the kth minimum weight on the path from node u to node v.

Output

For each operation,print its result.

Example

Input:
8 5
8 5
105 2 9 3 8 5 7 7
1 2        
1 3
1 4
3 5
3 6
3 7
4 8
2 5 1
2 5 2
2 5 3
2 5 4
7 8 2 
Output:
2
8
9
105


建个主席树。每个结点u的线段树代表的是从根结点到该结点所有点值的权值线段树。然后用dfs离线求出所有询问的两点的LCA,以及求出每个结点的真实父亲结点。于是每个询问的答案就是u结点+v结点-LCA结点-LCA父亲结点的权值线段树上第k大了。

  1 #include<bits/stdc++.h>
  2 #define clr(x) memset(x,0,sizeof(x))
  3 #define clr_1(x) memset(x,-1,sizeof(x))
  4 #define LL long long
  5 #define mod 1000000007
  6 using namespace std;
  7 const int N=2e5+10;
  8 struct qnode
  9 {
 10     int next,to,pos,lca,ith;
 11 }qedge[N*2];
 12 struct node
 13 {
 14     int next,to;
 15 }edge[N*2];
 16 int qhead[N],head[N],qcnt,ecnt,cnt,rnu;
 17 int vis[N],fa[N],value[N],ans[N],rfa[N];
 18 struct segt
 19 {
 20     int l,r,sum,lt,rt;
 21 }seg[N<<6];
 22 int root[N],pos[N];
 23 int n,m,k,u,v;
 24 void addedge(int u,int v)
 25 {
 26     edge[++ecnt].to=v;
 27     edge[ecnt].next=head[u];
 28     head[u]=ecnt;
 29     return ;
 30 }
 31 void addedgeq(int u,int v,int p,int val)
 32 {
 33     qedge[++qcnt].to=v;
 34     qedge[qcnt].next=qhead[u];
 35     qedge[qcnt].ith=val;
 36     qedge[qcnt].pos=p;
 37     qedge[qcnt].lca=0;
 38     qhead[u]=qcnt;
 39     return ;
 40 }
 41 int Find(int x)
 42 {
 43     if(x!=fa[x])
 44         fa[x]=Find(fa[x]);
 45     return fa[x];
 46 }
 47 void Union(int u,int v)
 48 {
 49     if(Find(u)!=Find(v))
 50     {
 51         fa[Find(v)]=Find(u);
 52     }
 53     return ;
 54 }
 55 void init(int &i,int l,int r)
 56 {
 57     seg[++cnt]=seg[i];
 58     i=cnt;
 59     seg[i].l=l;
 60     seg[i].r=r;
 61     if(l==r)
 62         return;
 63     int mid=(l+r)>>1;
 64     init(seg[i].lt,l,mid);
 65     init(seg[i].rt,mid+1,r);
 66     return ;
 67 }
 68 void update(int &i,int posi)
 69 {
 70     seg[++cnt]=seg[i];
 71     i=cnt;
 72     seg[i].sum++;
 73     if(seg[i].l==posi && seg[i].r==posi)
 74         return ;
 75     int mid=(seg[i].l+seg[i].r)>>1;
 76     if(mid>=posi)
 77         update(seg[i].lt,posi);
 78     if(mid<posi)
 79         update(seg[i].rt,posi);
 80     return ;
 81 }
 82 int query(int u,int v,int lca,int lcafa,int ith)
 83 {
 84     if(seg[u].l==seg[u].r)
 85         return seg[u].l;
 86     int ul=seg[u].lt,vl=seg[v].lt,lcal=seg[lca].lt,lcafal=seg[lcafa].lt;
 87     int lsum=seg[ul].sum+seg[vl].sum-seg[lcal].sum-seg[lcafal].sum;
 88     if(lsum>=ith)
 89         return query(ul,vl,lcal,lcafal,ith);
 90     else
 91         return query(seg[u].rt,seg[v].rt,seg[lca].rt,seg[lcafa].rt,ith-lsum);
 92 }
 93 void dfs(int u,int pre)
 94 {
 95     int num=lower_bound(pos+1,pos+rnu+1,value[u])-pos;
 96     update(root[u]=root[pre],num);
 97     rfa[u]=pre;
 98     vis[u]=1;
 99     for(int i=head[u];i!=-1;i=edge[i].next)
100     {
101         if(edge[i].to!=pre)
102         { 
103             dfs(edge[i].to,u);
104             Union(u,edge[i].to);
105         } 
106     }
107     for(int i=qhead[u];i!=-1;i=qedge[i].next)
108     {
109         if(vis[qedge[i].to]==1)
110             qedge[i].lca=Find(qedge[i].to);
111     }
112     return ;
113 }
114 void askans(int n)
115 {
116     for(int u=1;u<=n;u++)
117     {
118         for(int i=qhead[u];i!=-1;i=qedge[i].next)
119             if(qedge[i].lca!=0)
120             {
121                 ans[qedge[i].pos]=pos[query(root[u],root[qedge[i].to],root[qedge[i].lca],root[rfa[qedge[i].lca]],qedge[i].ith)];
122             }
123     }
124     return ;
125 }
126 int main()
127 {
128     while(scanf("%d%d",&n,&m)!=EOF)
129     {
130         qcnt=ecnt=cnt=0;
131         clr_1(head);
132         clr_1(qhead);
133         clr(vis); 
134         for(int i=1;i<=n;i++)
135         {
136             scanf("%d",&value[i]);
137             fa[i]=i;
138             pos[i]=value[i];
139         }
140         sort(pos+1,pos+n+1);
141         rnu=unique(pos+1,pos+n+1)-pos;
142         rnu--;
143         root[0]=0;
144         seg[0]=(segt){0,0,0,0,0};
145         for(int i=1;i<n;i++)
146         {
147             scanf("%d%d",&u,&v);
148             addedge(u,v);
149             addedge(v,u);
150         }
151         for(int i=1;i<=m;i++)
152         {
153             scanf("%d%d%d",&u,&v,&k);
154             addedgeq(u,v,i,k);
155             addedgeq(v,u,i,k);
156         }
157         init(root[0],1,rnu);
158         dfs(1,0);
159         askans(n);
160         for(int i=1;i<=m;i++)
161             printf("%d\n",ans[i]);
162     }
163     return 0;
164 }
View Code

 

hdu 5919

Sequence II

Time Limit: 9000/4500 MS (Java/Others)    Memory Limit: 131072/131072 K (Java/Others)
Total Submission(s): 2632    Accepted Submission(s): 702


Problem Description
Mr. Frog has an integer sequence of length n, which can be denoted as a1,a2,,an There are m queries.

In the i-th query, you are given two integers li and ri . Consider the subsequence ali,ali+1,ali+2,,ari .

We can denote the positions(the positions according to the original sequence) where an integer appears first in this subsequence as p(i)1,p(i)2,,p(i)ki (in ascending order, i.e.,p(i)1<p(i)2<<p(i)ki ).

Note that ki is the number of different integers in this subsequence. You should output p(i)ki2 for the i-th query.
 

Input
In the first line of input, there is an integer T (T2 ) denoting the number of test cases.

Each test case starts with two integers n (n2×105 ) and m (m2×105 ). There are n integers in the next line, which indicate the integers in the sequence(i.e., a1,a2,,an,0ai2×105 ).

There are two integers li and ri in the following m lines.

However, Mr. Frog thought that this problem was too young too simple so he became angry. He modified each query to li,ri(1lin,1rin) . As a result, the problem became more exciting.

We can denote the answers as ans1,ans2,,ansm . Note that for each test case ans0=0 .

You can get the correct input li,ri from what you read (we denote them as li,ri )by the following formula:
li=min{(li+ansi1) mod n+1,(ri+ansi1) mod n+1}

ri=max{(li+ansi1) mod n+1,(ri+ansi1) mod n+1}
 

Output
You should output one single line for each test case.

For each test case, output one line “Case #x: p1,p2,,pm ”, where x is the case number (starting from 1) and p1,p2,,pm is the answer.
 

Sample Input
2 5 2 3 3 1 5 4 2 2 4 4 5 2 2 5 2 1 2 2 3 2 4
 

Sample Output
Case #1: 3 3 Case #2: 3 1
 

 
这题让我想起了那个图:原来你也卡算法啊?是啊,我卡常。
mdzz卡空间的,我是习惯把l和r写在线段树结构体内的。。真的是太过分了。
这题让你必须在线求[l,r]中不同的数字的个数k,并把这些数字第一次出现的位置排个序,输出第[k/2]个位置。
那我们考虑按照1~n n个位置建立主席树,root[i]代表[i,n]这个区间对应主席树的根。那么如果某个位置的数字在他对应区间内是第一次出现的,那么该位置置1,否则置0。
那我们可以再记录一个head表,head[p]表示p这个数字上一次出现的位置,然后从n~1倒着建树,并且更新head表。那么后面查询的时候我们就可以查询root[l]的[l,r],O(logn)地得出k,并且在root[l]上O(logn)地查找第[k/2]个数了。
整体时间复杂度O(logn),空间复杂度是o(2*nlogn)差不多是n*37的空间。
  1 #include<bits/stdc++.h>
  2 #define clr(x) memset(x,0,sizeof(x))
  3 #define clr_1(x) memset(x,-1,sizeof(x))
  4 #define LL long long
  5 #define mod 1000000007
  6 #define INF 0x3f3f3f3f
  7 using namespace std;
  8 const int N=2e5+10;
  9 struct node
 10 {
 11     int lson,rson,sum;
 12 }tree[N*37];
 13 int root[N],a[N],head[N],cnt;
 14 void inito(int n)
 15 {
 16     tree[0]=(node){0,0,0};
 17     root[n+1]=0;
 18     cnt=0;
 19     return ;
 20 }
 21 void init(int &i,int l,int r)
 22 {
 23     tree[++cnt]=tree[i];
 24     i=cnt;
 25     tree[i]=(node){0,0,0};
 26     if(l==r)
 27         return;
 28     int mid=(l+r)>>1;
 29     init(tree[i].lson,l,mid);
 30     init(tree[i].rson,mid+1,r);
 31     return ;
 32 }
 33 void update(int &i,int pos,int val,int lt,int rt)
 34 {
 35     tree[++cnt]=tree[i];
 36     i=cnt;
 37     if(lt==pos && rt==pos)
 38     {
 39         tree[i].sum+=val;
 40         return ;
 41     }
 42     int mid=(lt+rt)>>1;
 43     if(pos<=mid)
 44         update(tree[i].lson,pos,val,lt,mid);
 45     else
 46         update(tree[i].rson,pos,val,mid+1,rt);
 47     tree[i].sum+=val;
 48     return ;
 49 }
 50 int Find(int i,int num,int lt,int rt)
 51 {
 52     if(lt==rt)
 53         return lt;
 54     int mid=(lt+rt)>>1;
 55     if(tree[tree[i].lson].sum>=num)
 56         return Find(tree[i].lson,num,lt,mid);
 57     else
 58         return Find(tree[i].rson,num-tree[tree[i].lson].sum,mid+1,rt);
 59 }
 60 int query(int i,int l,int r,int lt,int rt)
 61 {
 62     if(lt>=l && rt<=r)
 63     {
 64         return tree[i].sum;
 65     }
 66     int ans=0;
 67     int mid=(lt+rt)>>1;
 68     if(mid>=l)
 69         ans+=query(tree[i].lson,l,r,lt,mid);
 70     if(mid<r)
 71         ans+=query(tree[i].rson,l,r,mid+1,rt);
 72     return ans;
 73 }
 74 int T,n,m,k,p,ans,li,ri,l,r;
 75 int main()
 76 {
 77     scanf("%d",&T);
 78     for(int kase=1;kase<=T;kase++)
 79     {
 80         scanf("%d%d",&n,&m);
 81         inito(n);
 82         printf("Case #%d:",kase);
 83         for(int i=1;i<=n;i++)
 84         {
 85             scanf("%d",a+i);
 86             head[a[i]]=-1;
 87         }
 88         init(root[n+1],1,n);
 89         for(int i=n;i>=1;i--)
 90         {
 91             root[i]=root[i+1];
 92             if(head[a[i]]!=-1)
 93                 update(root[i],head[a[i]],-1,1,n);
 94             head[a[i]]=i;
 95             update(root[i],i,1,1,n);
 96         }
 97         ans=0;
 98         for(int i=1;i<=m;i++)
 99         {
100             scanf("%d%d",&li,&ri);
101             l=(li+ans)%n+1;
102             r=(ri+ans)%n+1;
103             if(l>r)
104                 swap(l,r);
105             k=query(root[l],l,r,1,n);
106             k=(k+1)/2;
107             ans=Find(root[l],k,1,n);
108             printf(" %d",ans);
109         }
110         printf("\n");
111     }
112     return 0;
113 }
View Code

 

posted @ 2017-02-20 15:47  hk_lin  阅读(704)  评论(0编辑  收藏  举报