题目:
poj 2352 Stars 数星星
题意:已知n个星星的坐标。每个星星都有一个等级,数值等于坐标系内纵坐标和横坐标皆不大于它的星星的个数。星星的坐标按照纵坐标从小到大的顺序给出,纵坐标相同时则按照横坐标从小到大输出。 (0 <= x, y <= 32000) 要求输出等级0到n-1之间各等级的星星个数。
分析:
这道题不难想到n平方的算法,即从纵坐标最小的开始搜,每次找它前面横坐标的值比它小的点的个数,两个for循环搞定,但是会超时。
所以需要用一些数据结构去优化,主要是优化找 横坐标比它小的点的个数 这里可以用线段树维护。我设sumv的意义是,从L到R(L,R代表横坐标的值)范围内的横坐标的个数。它的左儿子是L到M内的个数,右儿子是M+1到R内的个数。叶节点当L=R是,就是横坐标为L的点的个数。最后用vis数组记录每个等级的数量即可。
我一开始的做法是先输入完,然后直接构造所有节点的线段树,但是这样就不能同时判断纵坐标的大小,调试了好久,发现是错误的。
由于题目是按照纵坐标的大小,从小到大,同时横坐标从小到大给出的,因此,在一边输入的时候一个点一个点构造线段树就 不用 判断纵坐标的大小。所以只要维护一个横坐标的线段树,在输入的时候一边输,一边查询,一边更新,这样就可以了。在更新的时候,插入数据的方法是从根节点往下一点点更新,查询的方法就是普通的线段树查询。但还有一个小问题:在输入的时候你不能知道线段树的大小是多少,因为你不知道在整个数据中最大的那个横坐标。那我们只好慷慨一点,范围直接用maxn(32000),虽然处理小数据时浪费了点空间,但其实没事的。
查询和更新的时间复杂度都是logn。总算法的时间复杂度O(nlogn)。
附上代码:
1 #include<cstdio>
2 #include<iostream>
3 using namespace std;
4 const int maxn=40010;
5
6 int X[maxn],Y[maxn],n,sumv[maxn*4];
7 int vis[maxn];
8 int p;
9 void update(int o,int L,int R)
10 {
11 if(L==R) sumv[o]++;
12 else
13 {
14 int M=(L+R)/2;
15 if(p<=M) update(o*2,L,M);
16 else update(o*2+1,M+1,R);
17 sumv[o]++;
18 }
19 }
20
21 int ans;
22 void query(int o,int L,int R)
23 {
24 if(R<=p) ans+=sumv[o];
25 else if(p<L) return;
26 else
27 {
28 int M=(L+R)/2;
29 query(o*2,L,M);
30 query(o*2+1,M+1,R);
31 }
32 }
33
34 int main()
35 {
36 cin>>n;
37 for(int i=1;i<=n;i++)
38 {
39 cin>>X[i]>>Y[i];
40 p=X[i],ans=0;
41 query(1,0,maxn);
42 vis[ans]++;
43 update(1,0,maxn);
44 }
45 for(int i=0;i<=n-1;i++) cout<<vis[i]<<endl;
46 return 0;
47 }