nowcoder (树状数组+贡献计数+前缀和)
题目
https://ac.nowcoder.com/acm/contest/9033/D
大意
给数组,和整数k
每次询问一个区间,它的所有子区间出现次数最多的数正好等于k的区间数
数组长度和询问数都是3e5
题解
我们要统计的是正好,
那么如果可以计算出 <=k
,那么我们可以计算出<=k-1
然后想减少,只需要2倍时间
对于询问我们采取离线计算
下面问题变成如何计算 区间[l,r]
上满足 <=k
的方案数
考虑以 i 为起点,其对应的右侧<=k
的分界点split(i2)。
很容易发现,这个分界点是随着i增加,单调递增的,因为如果 i1 < i2
,那么[i2,split(i2)]
是最长的<=k
,也就是其最多出现次数为k,那么[i1,split(i2)]
的最多出现次数,大于等于k,且[i1,split(i2)+1]
大于等于k+1
因此split(i1)<=split(i2)
有这个单调性质,我们很容易能扫一遍在O(n)
时间内求出,每个左侧i的点,对应的右侧分界点
再一个O(n)
可以在不考虑选点范围的情况下,计算出 点对的前缀和presum[i] =presum[i-1]+(split(i)-i+1)
来看看和目标之间的差距
要求[l,r]
中间满足<=k
的点对数量
presum[r] - presum[l-1]
计算了左侧起点在[l,r]
的满足<=k
点对数量,但是没有对点对的右侧点进行限制
也就是需要去掉 左侧断点 [l,r]
内,但是右侧端点 >r
的点对
= sum[i = l->r][ split(i) > r? split(i) - r: 0 ]
询问很多,不可能每次去计算,想前缀和之类,却 r和i没有直接关系,因为i是原始数组下标,而r是询问中提供的
拆分一下, 这样 前面部分可以通过 前缀和的方式统计,而后面的部分,只需要统计左端点在[l,r],右端点超过r的个数,在处理具体询问的时候乘上r即可。
= sum[i = l->r][ split(i) > r? split(i): 0 ] - sum[i = l->r][ split(i) > r? r : 0 ]
由此r从大到小处理询问即可。 O(n)
关于区间统计,上树状数组就行了
代码
1 |
|
收获总结
=k
如果难算,可以变成计算<=k
和<=k-1
然后进行 相减- 离线处理询问
- 处理“不能”前缀和的部分,拆分成可前缀和的部分,和部分统计,询问时再乘积的部分分别处理,降低到O(n)
- 在有前缀和的代码里 用1-index比0-index方便一些