树状数组
作用:
1.快速求前缀和$O(log~n~)$
2.修改某一个数$O(log~n~)$
(折中)
基于二进制
第一个区间的长度是$x$二进制中最后一个$1$对应的次幂
第二个区间的长度是${x-2} ^ {i1} $二进制中最后一个$1$对应的次幂
以此类推…即 $(L,R]$的长度是R的二进制表示的最后一个$1$对应的次幂
这里需要引入一个lowbit()
函数:lobit(x) = x & -x
利用这个函数可以求出$x$的二进制表示的最后一个$1$对应的次幂。
则$(L , R]$可以表示为$[R - lowbit[R] + 1 , R]$,所以我们可以用$C[R]$表示该区间的总合
$C[x] = a[x - lowbit(x) + 1 , x]$ ,即以$x$为右端点的长度是$lowbit(x)$的区间内所有数的和
-
通过父节点找子节点:
$x$是父节点,先将$x$减去$1$,x的儿子的个数就是末尾1的个数,然后依次找出儿子。
$C[x] = a[x] + C[x-1] + C[x-1-lowbit(x-1)] + C[x-1-lowbit(x-1)-lowbit(x-1-lowbit(x-1))]…$ -
子节点找父节点(对应修改操作)
该操作直接影响到的点是唯一的!
$P = x + lowbit(x)$
因此树状数组的修改操作如下:
//如果对a[x]进行修改
for(int i = x ; i <= n ; i += lowbit(i)) tr[i] += c;
//查询a[1~x]的和
for(int i = x ; i ; i -= lowbit[i]) res += c[i]
#include <iostream>
#include <cstring>
using namespace std;
const int N = 200010;
int n;
int Greater[N] , lower[N];
int a[N] , tr[N];
int lowbit(int x)
{
return x & -x;
}
void add(int x , int c)//为第x个数加上c,这里为了表示是否出现,所以加的是1
{
for(int i = x ; i <= n ; i += lowbit(i)) tr[i] += c;
}
//求1~x的和,tr[x]存储的是区间[x−lowbit(x)+1,x]出现的数的次数,题目说y1到yn是1到n的排列,所以这个sum是用来求1~x中出现的数的个数
int sum(int x)
{
int res = 0;
for(int i = x ; i ; i -= lowbit(i)) res += tr[i];
return res;
}
int main()
{
scanf("%d" , &n);
for(int i = 1 ; i <= n ; i++) scanf("%d" , &a[i]);
for(int i = 1 ; i <= n ; i++)
{
int y = a[i];
Greater[i] = sum(n) - sum(y);//n~y+1之间已出现的数的个数
lower[i] = sum(y - 1);//1~y-1中已出现的数的个数
add(y , 1);//记录y这个数已经出现了
}
typedef long long LL;
memset(tr , 0 , sizeof tr);
LL res1 = 0 , res2 = 0;
for (int i = n; i; i -- )
{
int y = a[i];
res1 += Greater[i] * (LL)(sum(n) - sum(y));
res2 += lower[i] * (LL)(sum(y - 1));
add(y, 1);
}
printf("%lld %lld" , res1 , res2);
return 0;
}
为啥要memset ty数组啊,这步不太懂
因为要重新建树了,之前是从左向右遍历数组建树,之后要从右向左遍历数组建树
哦哦,原来如此,感谢
这个tr数组是怎么一步一步更新的呀/jk
tr[]就是利用add更新的呀,不断增加lowbit(i),看图有助于理解。
这道题里的更新方式是add(y,1),表示y这个点出现的次数加1,因为这道题需要统计点出现的次数
懂了懂了,谢谢大佬 /bx
就是去每次去添加已经出现过的数字,然后用
tr[n]
去减去tr[y]
就是用最大的减去已经出现过的就是比他大的是
sum(n)-sum(y)
喔 对
感谢
tr[]
数组不是0
或1
吧,tr[a[i]]
不应该保存的是1~a[i]
中 所有数出现的次数么$tr[x]$存储的是区间$[x-lowbit(x)+1,x]$出现的数的次数,题目说$y_{1}$到$y_{n}$是$1$到$n$的排列,
嗯嗯, 是这个意思,但是我不太懂你的
sum
函数注释那里,tr数组的值是0
或1
打错了😅 多谢指正
这个问题思考了两天,看了多个题解,终于想明白了!感谢大佬!点赞!
加油hh~
大佬 这里不是要判断 ai前面和后面有几个数大于ai 或者 小于ai吗 在哪里体现了
sum(x)
返回的是1~x
中已经出现的数的个数,因此要想得到比a[i]
小的数有几个就只用求sum(a[i]-1)
即可谢谢大佬
不用谢
看到这才懂出现是啥意思了
我当初也是这里没太理解