题目概述:
有N个物
每个物坐标x,y,坐标系以左下角为原点
每个物有一个参数lvl,其值等于坐标系中位于其左下方(含正左,正下,但不含其自身)物的个数
输入:
第一行N,其后N行x,y,数据只有一组,物会按纵坐标递增呈现,纵坐标相同时按横坐标递增呈现
限制:
0<=N<=15000;0<=x,y<=32000
输出:
每行一个整数,若当前为输出的第m行(m从0开始),则输出lvl为m的物的个数
样例输入:
5
1 1
5 1
7 1
3 3
5 5
样例输出:
1
2
1
1
0
讨论:
树状数组的第一次练习,在理解上遇到一些困难,手边的资料包括题解对该题数据结构的介绍都不太充分,不过理解后实现并没难度,实际上额还写了一个简洁的版本,不过那个并不适合做题解
至于树状数组具体是什么,这里不在赘述,理论部分网络上可轻松找到一大片
另外看讨论版该题可将数据离散化(简单说就是用一个很小的标号来表示x,以减小空间占用),不过对于这道水题,反而会使代码更不简洁
这道题由于输入的特殊性,纵坐标完全成了摆设,因为纵坐标按递增呈现,因此后呈现的纵坐标必然大于等于先呈现的,即只需要比较横坐标就能判断位置,折合到数据结构中,
1.lvl全写是level,沿用原题的称呼,这里tree是树状数组的原始容器,其值记录横坐标小于等于下标的物的个数,这也是树状数组的原始用法,lvl即是题目中要求的参数
2.这两行(额自己)在理解上并不是很容易,一步步看,横坐标小于等于x的物共有read(x)个,由于修改tree数组的操作在下一行,因此这时还没有将当前节点计算在内,因此read(x)值(从tree中计算得来)即是当前物的lvl值,由于最后输出的是同一lvl的物个数,这里便+1,下一行mod(x,1)将该物加到数组tree中,另外,由于输入的特殊性,只要是后呈现的物,之前所有横坐标小于等于这个物的,都会贡献到lvl中,也就是说,直接将当前的read(x)(横坐标小于等于x的物的个数)拿来便是lvl,因此直接+1便可解决问题,扩展一点,如果输入没有这个特殊性该怎么办?额考虑的是人为地将其排序,这比实现判断read(x)中有多少不能算或开到二维树状数组要容易的多,尽管可能会牺牲时间渐近复杂度(排序的复杂度很可能高于数组操作)
题解状态:
348K,125MS,C++,690B
#include<algorithm>//在C++模式下,包含algorithm头文件便包含诸多常用头文件
using namespace std;
const int inf = 0x6f6f6f6f;
const int MAXN = 32005;
int tree[MAXN], lvl[MAXN];//参见讨论1
inline int lb(int a)//lowbit,这个东西的作用在树状数组资料中不会没写,不再赘述
{
return a&(-a);
}
inline int read(int a)//读出到下标从1(含)到a(含)的元素的值的和,其实叫sum更合适,但sum属常用词汇,同时资料里也常称之为read
{
int sum = 0;
while (a > 0) {
sum += tree[a];
a -= lb(a);
}
return sum;
}
inline void mod(int a, int mod)//modify,利用加减法修改一个节点的值,并将其父节点值一并改变,该题中由于mod值恒为1,可将其具体化,这里只是方便作模版
{
while (a <= MAXN) {//试图将MAXN改成N(令N为全局变量)只会得到一个错误的结果
tree[a] += mod;
a += lb(a);
}
}
inline void fun(int N)
{
for (int p = 0; p < N; p++) {
int x, y;
scanf("%d%d", &x, &y);//input//从这里之后y就没用了
++x;//由讨论版,数据中会有x=0的情况,而树状数组的0下标不使用,因此这里+1避开这一情况,不需要其他额外处理
++lvl[read(x)];
mod(x, 1);//参见讨论2
}
for (int p = 0; p < N; p++)
printf("%d\n", lvl[p]);//output
}
int main(void)
{
//freopen("vs_cin.txt", "r", stdin);
//freopen("vs_cout.txt", "w", stdout);
int N;
scanf("%d", &N);//input
fun(N);
}
EOF