题目:
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)。
附上代码:
#include<cstdio>
#include<iostream>
using namespace std;
const int maxn=; int X[maxn],Y[maxn],n,sumv[maxn*];
int vis[maxn];
int p;
void update(int o,int L,int R)
{
if(L==R) sumv[o]++;
else
{
int M=(L+R)/;
if(p<=M) update(o*,L,M);
else update(o*+,M+,R);
sumv[o]++;
}
} int ans;
void query(int o,int L,int R)
{
if(R<=p) ans+=sumv[o];
else if(p<L) return;
else
{
int M=(L+R)/;
query(o*,L,M);
query(o*+,M+,R);
}
} int main()
{
cin>>n;
for(int i=;i<=n;i++)
{
cin>>X[i]>>Y[i];
p=X[i],ans=;
query(,,maxn);
vis[ans]++;
update(,,maxn);
}
for(int i=;i<=n-;i++) cout<<vis[i]<<endl;
return ;
}