zoj 2112 (主席树,树状数组套线段树)

本文介绍了一种高效解决区间第K大值查询问题的方法,采用树状数组结合主席树的技术实现动态更新与快速查询,适用于数值范围较大且频繁修改的情况。
Dynamic Rankings

Time Limit: 10 Seconds       Memory Limit: 32768 KB

The Company Dynamic Rankings has developed a new kind of computer that is no longer satisfied with the query like to simply find the k-th smallest number of the given N numbers. They have developed a more powerful system such that for N numbers a[1], a[2], ..., a[N], you can ask it like: what is the k-th smallest number of a[i], a[i+1], ..., a[j]? (For some i<=j, 0<k<=j+1-i that you have given to it). More powerful, you can even change the value of some a[i], and continue to query, all the same.

Your task is to write a program for this computer, which

- Reads N numbers from the input (1 <= N <= 50,000)

- Processes M instructions of the input (1 <= M <= 10,000). These instructions include querying the k-th smallest number of a[i], a[i+1], ..., a[j] and change some a[i] to t.


Input

The first line of the input is a single number X (0 < X <= 4), the number of the test cases of the input. Then X blocks each represent a single test case.http://www.sn180.com/buyer/buyview/1115832741.html

The first line of each block contains two integers N and M, representing N numbers and M instruction. It is followed by N lines. The (i+1)-th line represents the number a[i]. Then M lines that is in the following format

Q i j k or
C i t

It represents to query the k-th number of a[i], a[i+1], ..., a[j] and change some a[i] to t, respectively. It is guaranteed that at any time of the operation. Any number a[i] is a non-negative integer that is less than 1,000,000,000.

There're NO breakline between two continuous test cases.


Output

For each querying operation, output one integer to represent the result. (i.e. the k-th smallest number of a[i], a[i+1],..., a[j])

There're NO breakline between two continuous test cases.http://www.sn180.com/buyer/buyview/1115821323.html


Sample Input

2
5 3
3 2 1 4 7
Q 1 4 3
C 2 6
Q 2 5 3
5 3
3 2 1 4 7
Q 1 4 3
C 2 6
Q 2 5 3


Sample Output

3
6
3
6


题意:查找区间第K大值,值可修改

分析:

每一棵线段树是维护每一个序列前缀的值在任意区间的个数,
如果还是按照静态的来做的话,那么每一次修改都要遍历O(n)棵树,
时间就是O(2*M*nlogn)->TLE
考虑到前缀和,我们通过树状数组来优化,即树状数组套主席树,
每个节点都对应一棵主席树,那么修改操作就只要修改logn棵树,
o(nlognlogn+Mlognlogn)时间是可以的,
但是直接建树要nlogn*logn(10^7)会MLE
我们发现对于静态的建树我们只要nlogn个节点就可以了,
而且对于修改操作,只是修改M次,每次改变俩个值(减去原先的,加上现在的)
也就是说如果把所有初值都插入到树状数组里是不值得的,
所以我们分两部分来做,所有初值按照静态来建,内存O(nlogn),
而修改部分保存在树状数组中,每次修改logn棵树,每次插入增加logn个节点

代码:

[cpp]  view plain  copy
  1. //#pragma comment(linker,"/STACK:102400000,102400000")  
  2. #include <iostream>  
  3. #include <cstdio>  
  4. #include <cstring>  
  5. #include <cmath>  
  6. #include <algorithm>  
  7. #include <stack>  
  8. #include <map>  
  9. #include <set>  
  10. #include <vector>  
  11. #include <queue>  
  12. #define mem(p,k) memset(p,k,sizeof(p));  
  13. #define pb push_back  
  14. //#define lson l,m,rt<<1  
  15. //#define rson m+1,r,rt<<1|1  
  16. #define inf 0x3f3f3f3f  
  17. #define ll long long  
  18. using namespace std;  
  19. const int N=50010 ;  
  20. struct SD{  
  21.     int flag,l,r,k;  
  22. }que[10010];  
  23. int T,n,q,len,tot;  
  24. int num[N],head1[N],head2[N];  
  25. int tree[N*50],lson[N*50],rson[N*50],use[N];  
  26. char s[2];  
  27. vector<int> vec;  
  28.   
  29. int Hash(int k){  
  30.     //cout<<lower_bound(vec.begin(),vec.end(),k)-vec.begin()+1;  
  31.     return lower_bound(vec.begin(),vec.end(),k)-vec.begin()+1;  
  32. }  
  33. void update(int pre,int &now,int k,int val,int l,int r){  
  34.     now=tot++;  
  35.     tree[now]=tree[pre]+val;  
  36.     lson[now]=lson[pre];rson[now]=rson[pre];  
  37.     if(l==r)return;  
  38.     int m=(l+r)>>1;  
  39.     if(k<=m)update(lson[pre],lson[now],k,val,l,m);  
  40.     else update(rson[pre],rson[now],k,val,m+1,r);  
  41. }  
  42. int lowbit(int i){ return -i&i; }  
  43.   
  44. void add(int k,int f,int val){  
  45.     for(int i=k;i<=n;i+=lowbit(i)){  
  46.         update(head2[i],head2[i],f,val,1,len);  
  47.     }  
  48. }  
  49.   
  50. int sum(int k){  
  51.     int s=0;  
  52.     for(int i=k;i>0;i-=lowbit(i)){  
  53.         s+=tree[lson[use[i]]];  
  54.     }  
  55.     return s;  
  56. }  
  57.   
  58. int Query(int L,int R,int k){  
  59.     int s1=head1[L-1],s2=head1[R],l=1,r=len;  
  60.     for(int i=L-1;i>0;i-=lowbit(i))use[i]=head2[i];  
  61.     for(int i=R;i>0;i-=lowbit(i))use[i]=head2[i];  
  62.     //cout<<L<<R<<"==="<<endl;  
  63.     while(l<r){  
  64.         int tmp=sum(R)-sum(L-1)+tree[lson[s2]]-tree[lson[s1]];  
  65.         int m=(l+r)>>1;//cout<<tmp<<"  "<<k<<endl;  
  66.   
  67.         if(k<=tmp){  
  68.             r=m;  
  69.             s1=lson[s1];s2=lson[s2];  
  70.             for(int i=L-1;i>0;i-=lowbit(i))use[i]=lson[use[i]];  
  71.             for(int i=R;i>0;i-=lowbit(i))use[i]=lson[use[i]];  
  72.         }  
  73.         else{  
  74.             k-=tmp;  
  75.             l=m+1;  
  76.             s1=rson[s1],s2=rson[s2];  
  77.             for(int i=L-1;i>0;i-=lowbit(i))use[i]=rson[use[i]];  
  78.             for(int i=R;i>0;i-=lowbit(i))use[i]=rson[use[i]];  
  79.         }//cout<<l<<r<<endl;  
  80.     }  
  81.     //cout<<"  ~~~~~~ "<<endl;  
  82.     return vec[l-1];  
  83. }  
  84.   
  85. int main()  
  86. {  
  87.     cin>>T;  
  88.     while(T--){  
  89.         cin>>n>>q;  
  90.         tot=1;  
  91.         vec.clear();  
  92.         mem(tree,0);  
  93.         mem(lson,0);  
  94.         mem(rson,0);  
  95.         for(int i=1;i<=n;i++)scanf("%d",num+i),vec.pb(num[i]);  
  96.         for(int i=1;i<=q;i++){  
  97.             scanf("%s",s);  
  98.             if(s[0]=='Q'){  
  99.                 que[i].flag=0;  
  100.                 scanf("%d%d%d",&que[i].l,&que[i].r,&que[i].k);  
  101.             }  
  102.             else{  
  103.                 que[i].flag=1;  
  104.                 scanf("%d%d",&que[i].l,&que[i].k);  
  105.                 vec.pb(que[i].k);  
  106.             }  
  107.         }  
  108.         sort(vec.begin(),vec.end());  
  109.         vec.erase(unique(vec.begin(),vec.end()),vec.end());  
  110.         len=vec.size();  
  111.         //for(int i=0;i<vec.size();i++)cout<<vec[i]<<endl;  
  112.         head1[0]=0;  
  113.         for(int i=1;i<=n;i++){  
  114.             update(head1[i-1],head1[i],Hash(num[i]),1,1,len);  
  115.         }  
  116.   
  117.         for(int i=1;i<=n;i++){  
  118.             head2[i]=0;  
  119.         }  
  120.         for(int i=1;i<=q;i++){  
  121.             if(que[i].flag){  
  122.                 add(que[i].l,Hash(num[que[i].l]),-1);  
  123.                 add(que[i].l,Hash(que[i].k),1);  
  124.                 num[que[i].l]=que[i].k;  
  125.             }  
  126.             else{  
  127.                 printf("%d\n",Query(que[i].l,que[i].r,que[i].k));  
  128.             }  
  129.         }  
  130.   
  131.     }  
  132.   
  133.     return 0;  
  134. }  
### ZOJ 1088 线段树 解题思路 #### 题目概述 ZOJ 1088 是一道涉及动态维护区间的经典问题。通常情况下,这类问题可以通过线段树来高效解决。题目可能涉及到对数组的区间修改以及单点查询或者区间查询。 --- #### 线段树的核心概念 线段树是一种基于分治思想的数据结构,能够快速处理区间上的各种操作,比如求和、最大值/最小值等。其基本原理如下: - **构建阶段**:通过递归方式将原数组划分为多个小区间,并存储在二叉形式的节点中。 - **更新阶段**:当某一段区间被修改时,仅需沿着对应路径向下更新部分节点即可完成全局调整。 - **查询阶段**:利用懒惰标记(Lazy Propagation),可以在 $O(\log n)$ 时间复杂度内完成任意范围内的计算。 具体到本题,假设我们需要支持以下两种主要功能: 1. 对指定区间 `[L, R]` 执行某种操作(如增加固定数值 `val`); 2. 查询某一位置或特定区间的属性(如总和或其他统计量)。 以下是针对此场景设计的一种通用实现方案: --- #### 实现代码 (Python) ```python class SegmentTree: def __init__(self, size): self.size = size self.tree_sum = [0] * (4 * size) # 存储区间和 self.lazy_add = [0] * (4 * size) # 延迟更新标志 def push_up(self, node): """ 更新父节点 """ self.tree_sum[node] = self.tree_sum[2*node+1] + self.tree_sum[2*node+2] def build_tree(self, node, start, end, array): """ 构建线段树 """ if start == end: # 到达叶节点 self.tree_sum[node] = array[start] return mid = (start + end) // 2 self.build_tree(2*node+1, start, mid, array) self.build_tree(2*node+2, mid+1, end, array) self.push_up(node) def update_range(self, node, start, end, l, r, val): """ 区间更新 [l,r], 加上 val """ if l <= start and end <= r: # 当前区间完全覆盖目标区间 self.tree_sum[node] += (end - start + 1) * val self.lazy_add[node] += val return mid = (start + end) // 2 if self.lazy_add[node]: # 下传延迟标记 self.lazy_add[2*node+1] += self.lazy_add[node] self.lazy_add[2*node+2] += self.lazy_add[node] self.tree_sum[2*node+1] += (mid - start + 1) * self.lazy_add[node] self.tree_sum[2*node+2] += (end - mid) * self.lazy_add[node] self.lazy_add[node] = 0 if l <= mid: self.update_range(2*node+1, start, mid, l, r, val) if r > mid: self.update_range(2*node+2, mid+1, end, l, r, val) self.push_up(node) def query_sum(self, node, start, end, l, r): """ 查询区间[l,r]的和 """ if l <= start and end <= r: # 完全匹配 return self.tree_sum[node] mid = (start + end) // 2 res = 0 if self.lazy_add[node]: self.lazy_add[2*node+1] += self.lazy_add[node] self.lazy_add[2*node+2] += self.lazy_add[node] self.tree_sum[2*node+1] += (mid - start + 1) * self.lazy_add[node] self.tree_sum[2*node+2] += (end - mid) * self.lazy_add[node] self.lazy_add[node] = 0 if l <= mid: res += self.query_sum(2*node+1, start, mid, l, r) if r > mid: res += self.query_sum(2*node+2, mid+1, end, l, r) return res def solve(): import sys input = sys.stdin.read data = input().split() N, Q = int(data[0]), int(data[1]) # 数组大小 和 操作数量 A = list(map(int, data[2:N+2])) # 初始化数组 st = SegmentTree(N) st.build_tree(0, 0, N-1, A) idx = N + 2 results = [] for _ in range(Q): op_type = data[idx]; idx += 1 L, R = map(int, data[idx:idx+2]); idx += 2 if op_type == 'Q': # 查询[L,R]的和 result = st.query_sum(0, 0, N-1, L-1, R-1) results.append(result) elif op_type == 'U': # 修改[L,R]+X X = int(data[idx]); idx += 1 st.update_range(0, 0, N-1, L-1, R-1, X) print("\n".join(map(str, results))) solve() ``` --- #### 关键点解析 1. **初始化与构建**:在线段树创建过程中,需要遍历输入数据并将其映射至对应的叶子节点[^1]。 2. **延迟传播机制**:为了优化性能,在执行批量更新时不立即作用于所有受影响区域,而是记录更改意图并通过后续访问逐步生效[^2]。 3. **时间复杂度分析**:由于每层最多只访问两个子分支,因此无论是更新还是查询都维持在 $O(\log n)$ 范围内[^3]。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值