Loading

线段树+树状数组

线段树+树状数组

线段树能解决的问题:

单点修改(加),区间加、乘、推平,单点(区间)查询最大(小)值、和、平(立)方和、\(gcd\)、最大子段和

树状数组能解决的问题:

区间修改单点查询,单点修改区间查询,通过维护值域求大于等于当前值的数的个数(可用于求逆序对)

P5278

题意

给你一个序列,要求支持:

  • 单点修改
  • 查询一个区间 \([l,r]\) 是否可重排为公差为 \(k\) 的等差数列

思路

线段树维护区间最大值 \(mx\) ,区间最小值 \(mn\),区间和 \(sum\)

\(a[l\cdots r]\) 可重排为公差为 \(k\) 的等差数列,则可得到以下几个关系:

  • \(mx-mn=(r-l)\cdot k\)
  • \(sum=(mx+mn)\cdot (r-l+1)/2\)\(sum\cdot 2=(mx+mn)\cdot (r-l+1)\)

然后我这么维护了

然后我过了 \((\)数据弱\()\)

实际上应该维护相邻两个数的差的 \(gcd\),它等于 \(k\)

但是已经不需要了

code

P6617

题意

给你一个序列,要求支持:

  • 单点修改

  • 查询一个区间 \([l,r]\) 内是否存在 \(x,y\) 使得 \(a_x +a_y =w\) (\(w\) 已知)

思路

\(next[i]\) 表示最小的满足 \(a_i+a_{next[i]}=w\)

操作二等价于求 \(min_{i=l}^{r}next[i]<=r\)

并且由于 \(next[i]>i\),题目就是在求 \(min_{i=l}^{n}next[i]<=r\)

我们可以用线段树维护这个求后缀最小值的操作

而且对于所有 \(next[i]\) 相同的 \(i\),只用维护最大的 \(i\)\(next[i]\)就行

我们用 \(set\) 维护前驱后继的查询即可

小细节:把 \(x\)\(w-x\) 维护在同一个 \(set\) 中只会影响前驱后继

code

P5069

题意

给你一个序列,要求支持:

  • 单点修改

  • 每次修改后问对序列重复进行以下操作,需要进行几次操作才能使序列变为全 \(0\)

    选出序列中最大值的出现位置,若有多个最大值则选位置标号最小的一个,设位置为 \(x\),则将 \(a_{x-1},a_x,a_{x+1}\) 的值减 \(1\),如果序列中存在小于 \(0\) 的数,则把对应的数改为 \(0\)

思路

考虑会修改哪些位置

可以把整个序列分割成许多个单调递增子序列,对于每一个单增子序列中有且仅有和最大值奇偶性相同的位置会执行操作二

所以按奇偶性维护两个树状数组

然后考虑如何修改

发现每个修改的地方都只会影响到上一个单增子序列和下一个单增子序列

\(set\) 维护所有单增子序列最大值的位置,用来查找前驱后继

\(P.S.\) :细节很多

code

P2894

题意

给你一个序列,要求支持:

  • \(x\) 找到最左侧长度为 \(x\) 的连续 \(0\) 序列,并将其推平成 \(1\)
  • 将一段区间推平成 \(0\)

思路

操作一就是一个类似于最大子段和的操作 \(+\) 推平操作

操作二是推平操作

线段树可以维护

code

P2572

题意

给你一个序列,要求支持:

  • 区间推平为 \(0\)
  • 区间推平为 \(1\)
  • 区间取反
  • 区间求和
  • 求区间内最多有多少个 \(1\)

思路

显然是线段树,但很恶心

code

P6186

题意

给定一个 \(1 ∼ n\) 的排列 \(p_i\),要求支持:

  • 交换操作:给定 \(x\),将当前排列中的第 \(x\) 个数与第 \(x+1\) 个数交换位置

  • 询问操作:给定 \(k\),请你求出当前排列经过 \(k\) 轮冒泡排序后的逆序对个数
    对一个长度为 \(n\) 的排列 \(p_i\) 进行一轮冒泡排序的伪代码如下:

for i = 1 to n-1:
  if p[i] > p[i + 1]:
    swap(p[i], p[i + 1])

思路

先考虑操作 \(2\)

我们先通过树状数组求出原序列的逆序对个数,在通过研究冒泡排序处理出 \(k\) 次冒泡排序造成的影响

思考冒泡排序的本质,可以发现:

若一个数前面没有比它大的数,那么它会动,然后停下来

所以我们记录 \(b[i]\) 表示前面有多少个数比 \(a[i]\) 大,每一轮 \(b[i]\) 都会减少 \(1\)

记录 \(d[i]\) 表示 \(b[i]\) 的个数,这样就可以知道每一轮有多少个数能动,即每一轮减少多少逆序对

通过树状数组实现一个差分的操作就可以求出结果

考虑操作 \(1\)

每次交换 \(i,i+1\) 的时候影响的只有 \(i,i+1\)

  • \(a[i]<a[i+1]\) ,交换后逆序对个数 \(+1\) ,但交换后的 \(b[i+1]\) 能动的时候,此时为 \(b[i+1]+1\) 轮,就 \(-1\)
  • \(a[i]>a[i+1]\) ,交换后逆序对个数 \(-1\) ,但交换后的 \(b[i]\) 能动的时候,此时为 \(b[i]-1\) 轮,就 \(+1\)

并依照 \(a[i]\)\(a[i+1]\) 的大小关系修改 \(b[i+1],b[i]\)

由于这个影响只能到 \(b[i+1]\) 轮,所以之后要改回来

code

posted @ 2022-07-02 12:35  Into_qwq  阅读(16)  评论(0编辑  收藏  举报