【SDUT OJ 2610】 Boring Counting(主席树)
Boring Counting
Time Limit: 3000ms Memory limit: 65536K 有疑问?点这里^_^
题目描述
In this problem you are given a number sequence P consisting of N integer and Pi is the ith element in the sequence. Now you task is to answer a list of queries,
for each query, please tell us among [L, R], how many Pi is not less than A and not greater than B( L<= i <= R). In other words, your task is to count the number of Pi (L <= i <= R, A <= Pi <= B).
输入
In the first line there is an integer T (1 < T <= 50), indicates the number of test cases.
For each case, the first line contains two numbers N and M (1 <= N, M <= 50000), the size of sequence P, the number of queries. The second line contains N numbers Pi(1 <= Pi <= 10^9), the number sequence P. Then there are M lines, each line contains four
number L, R, A, B(1 <= L, R <= n, 1 <= A, B <= 10^9)
输出
For each case, at first output a line ‘Case #c:’, c is the case number start from 1. Then for each query output a line contains the answer.
示例输入
1 13 5 6 9 5 2 3 6 8 7 3 2 5 1 4 1 13 1 10 1 13 3 6 3 6 3 6 2 8 2 8 1 9 1 9
示例输出
Case #1: 13 7 3 6 9
提示
来源
2013年山东省第四届ACM大学生程序设计竞赛
示例程序
主席树——从刚进ACM没几个月就经常看到各大ACM群里这个词时不时冒个泡,而且是近几年新发现的一种数据结构。
从当时对这个东西就一直是很崇拜的姿态(90°仰望
昨天听Hongfeng巨一讲,立马醍醐灌顶茅塞顿开……
首先 主席树使用来干什么——求解静态区间第k大
主席树实现原理——用空间换时间,对于n个数,从左到右依次记录,这样得到n棵线段树构成主席树。
线段树中区间的含义——将所有n个数排序离散化后,从小到大各个数的编号构成的区间。
线段树的意义——求加入到当前数为止,所有区间中已经出现了的数的个数。
线段树做好后,会发现每次建一棵树复杂度会很高很高。
同时你会发现,加入第i个数的时候,此时需要建立新线段树,同时线段树是二叉结构,第i个线段树其实可以由第i-1棵线段树推出,这样其实改变了的节点只有log2(n)个。
即为从根到叶子的一条链。这样对于重复(未改变)的节点,直接指向i-1棵树相应位置的节点即可。
这样当求解从左到右边第i个数间大于x的数,遍历第i个线段树,找到所有右界小于等于x的区间,统计出累加和即可
当求[L,R]区间内大于x的数,遍历第R个线段树,用答案减去遍历第L个线段树的答案即可
当求[L,R]区间内第k大的数,通过补的方法,用右区间不断补足k,当较大的数出现够k个时,即得到答案
对于此题 求[L,R]区间内[A,B]范围内的数的个数。遍历直到区间[l,r] 满足 p[l] >= A, p[r] <= B即可(p为离散化后的数存放的数组)
代码如下:
#include <iostream> #include <cmath> #include <vector> #include <cstdlib> #include <cstdio> #include <cstring> #include <queue> #include <stack> #include <list> #include <algorithm> #include <map> #include <set> #define LL long long #define Pr pair<int,int> #define fread() freopen("data1.in","r",stdin) #define fwrite() freopen("out.out","w",stdout) using namespace std; const int INF = 0x3f3f3f3f; const int msz = 10000; const int mod = 1e9+7; const double eps = 1e-8; struct Node { //当前区间内出现的数的个数 int v; //左节点 右节点编号 int l,r; }; Node nd[2333333]; //原数 离散化 int num[55555],qt[55555]; //n棵线段树的根节点 int tm[55555]; int tp,n,tot; //建立新节点 返回下标 int NewNode(int x) { nd[tp].v = x; nd[tp].l = nd[tp].r = -1; return tp++; } //初始化第一棵线段树(所有区间均为0) int init(int l,int r) { if(l == r) return NewNode(0); int mid = (l+r)>>1; int rt = NewNode(0); nd[rt].l = init(l,mid); nd[rt].r = init(mid+1,r); return rt; } //建立线段树 int Build(int root,int l,int r,int x) { //当前区间中新出现一个数——x int rt = NewNode(nd[root].v+1); //为叶子节点时跳出 if(l == r) return rt; int mid = (l+r)>>1; if(x <= qt[mid]) { nd[rt].r = nd[root].r; nd[rt].l = Build(nd[root].l,l,mid,x); } else { nd[rt].l = nd[root].l; nd[rt].r = Build(nd[root].r,mid+1,r,x); } return rt; } //查询某[l,r]内[low,high]范围内数的个数 int Search(int root,int l,int r,int low,int high) { //注意特判一下 否则会停不下来…… if(qt[l] > high || qt[r] < low) return 0; if(qt[l] >= low && qt[r] <= high) return nd[root].v; int mid = (l+r)>>1; if(qt[mid] >= high) return Search(nd[root].l,l,mid,low,high); else if(qt[mid+1] <= low) return Search(nd[root].r,mid+1,r,low,high); else return Search(nd[root].l,l,mid,low,high)+Search(nd[root].r,mid+1,r,low,high); } int main() { //fread(); //fwrite(); int t,q; int st,en,low,high; scanf("%d",&t); for(int z = 1; z <= t; ++z) { tp = 0; scanf("%d%d",&n,&q); for(int i = 1; i <= n; ++i) { scanf("%d",&num[i]); qt[i] = num[i]; } sort(qt+1,qt+n+1); tot = unique(qt+1,qt+n+1)-qt-1; tm[0] = init(1,tot); for(int i = 1; i <= n; ++i) tm[i] = Build(tm[i-1],1,tot,num[i]); printf("Case #%d:\n",z); while(q--) { scanf("%d%d%d%d",&st,&en,&low,&high); printf("%d\n",Search(tm[en],1,tot,low,high)-Search(tm[st-1],1,tot,low,high)); } } return 0; }