今天继续看了一些题解,继续学习了一下树状数组。有关树状数组的各种题目类型,在学习日记15中,已经总结的差不多了,因为那是第一天看树状数组的题目,并且基本看完了一个人写的所有有关树状数组的博客,在之后的学习和看题解的过程中,我又发现了新的题目类型,还有之前的题目类型有的需要扩充,当时认知有限,总结的不够到位。现在补充一下。
一。离线操作预处理。二。二分和树状数组。这个和素数预处理差不多,都是要先把数据存在一个数组里,然后直接可以随便调用,但这里是直接全部输出。
一般情况下这种题会用到结构体,并且都与区间有很大关系。
比如一道题是,在一段数据中,求一个区间内,不重复的数的和。
vis[ k ]代表的是k上次出现的位置,也是判断是否进入数组的依据。
这个时候就要用结构体吧每个区间都记录下来,q[ k ] . l ,q[ k ] . r q[ k ] . id ;
然后按照 r 的大小从小到大进行升序排序。
然后,有几个区间就要循环几次,求几次 ans [ id ] 代表的是不重复的和。
就是把每一组区间循环一边,找出每个区间的ans。
每次循环都是从上一次循环结束的位置开始,到这一次位置的 r 位置。
循环里面有一个while + if 判断,while循环 调出数据 a[ k ],if 判断的是这个a [ k ]是否进入过数据如果进入过,就要add(vis[ a[ k ] ] ,-a[ k ]) ;
不管有没有进去过,都会执行下面的操作。
vis[ a[ k ] ] =k;
add(k ,a[ k ]);
每次一个区间循环结束都会把数据存到一个数组里。
ans [ q[ k ] . id ] = sum(q[ k ] . r)- sum(q[ k ] . l );
最后循环把所有区间的和输出就可以了。
由于我写的这个内容的笔记,都是汉字写的,很少代码。这里也就都用汉字说明了。
三。计算连续上升或下降的n元子序列的数量。(对前面的补充)由于树状数组本身的特性,递增的,所以它是可以被二分的,这也就使得在使用了二分之后的树状数组效率更高。
有个题是,很多牛都有号码,连续的,给你一串数,代表的第k 位置的牛前面有多少比它小的号码。要求输出牛的号码顺序。
这个题从前面看,看不出,如果倒着看,最后一个数前面比他小的数的个数 n 有了,那么这个号码是多少已经确定了,n+1。所以从后面推到前面就很简单了。
可以用树状数组把1 到n 的数存入树状数组,然后倒着寻找号码,每次寻找号码都用二分。把树状数组的1 到n二分,能够很快的找出来,要不然数据量大了之后容易超时。
应该有的题目二分的对象不一样,目前还未看到这种题目,估计快了。
先把学习日记15中的内容引入。
连续上升或下降的n元子序列的求法。(这里说四元上升子序列)
这种求法,只能求规定的元数的子序列。这里的数目较小,如果元数 比较大的话,可以用一个循环来代替,一遍遍的写这个dp过程。可能用到的知识点 1.离散化(如果数据过大)2 dp(当n>3)时,比如现在是四。3 树状数组 (n个) 4 高精度(如果64位都不够用的话)
输入数据为a[ i ]。
下面的 x y都是a [ i ] 。。。 sum()不是树状数组的函数了,是对里面的所有符合条件的数组求和。
dp过程
dp[ x ][ 2 ] = sum(dp[ y ][ 1 ]);
dp[ x ][ 3 ] = sum(dp[ y ][ 2 ]);
dp[ x ][ 4 ] = sum(dp[ y ][ 3 ]);
其中dp[ x ][ 2 ] 表示的意思是,x为子序列最后一个数字时的二元上升子序列的个数。
因为所有的3元都是来自于之前的二元,所以可以用这个来实现。
其中的树状数组有n个,每次输入一个数据,都需要对所有的树状数组进行更新。
就是把其中的数字换成一个变量 k。
for(int k=2;k<=N;k++)
{
dp[ x ][ k ] = sum(dp[ y ][ k-1 ]);
}
这样的话,求解很多元的也可以轻松应对了。
还有,如果这个题,再变一下,变成求解上升子序列的数目,那,只需要把dp公式再改一下,就可以了。
dp[ x ][ k ] = sum(dp[ y ][ j ]);其中 j<k. sum()是对里面的所有符合条件的数组求和。
这样的话,有关上升序列,下降序列,要求多元,少元,一般这类题都可以求解了。
四。树状数组和搜索 dfs()。
一般这类综合题,分开每个知识点考察的都不是很难。
这种组合,一般是用dfs,求出区间或者点,然后再用树状数组求和。可能单独用一个时间不允许。
由于我没整理这种题的笔记,这里就不举例了。