分块区间修改区间查询

Problem’s Website

  • 模板_线段树2

    Solution

  • 这道题可以用多种方法解决,例如线段树、树状数组,这些内容本菜鸡曾经写过,可以去我的其他Blog查看,本篇题解说一些分块做法。
  • 分块是一种根号数据结构,优雅的暴力,时间复杂度为$\Theta(m \sqrt{n})$,大意就是把整个序列分成$\sqrt{n}$块,带两个标记,一个乘法标记、加法标记,每次区间修改$[x,y]$把两个元素所在的块中下传标记,修改时,如果$x,y$在同一块内,就维护区间修改,否则就把在一个块的内容区间修改,把两边剩余的块暴力修改。
  • 听到上面的解释可能有点懵,我把代码中的变量解释一下,然后详解在下面的Code中。
    • $sum[i]$为第$i$块的区间和。
    • $l[i], r[i]$为第$i$块的左右端点。
    • $pos[i]$为第i个元素在第几个块。
    • $add[i]mul[i]$为加法乘法标记。
    • $tot[i]$为第$i$个块的元素个数。

      Code

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      82
      83
      84
      85
      86
      87
      88
      89
      90
      91
      92
      93
      94
      95
      96
      97
      98
      99
      100
      101
      102
      103
      104
      105
      106
      107
      108
      109
      110
      111
      112
      113
      114
      115
      116
      117
      118
      119
      120
      121
      122
      123
      124
      #include<iostream>
      #include<cstdio>
      #include<cstring>
      #include<algorithm>
      #include<cmath>
      #define gc getchar()
      using namespace std;
      typedef long long ll;
      const int Maxn = 100000 + 10;
      ll sc() {
      ll xx = 0, ff = 1; char cch = gc;
      while (cch < '0' || cch > '9') {
      if (cch == '-') ff = -1; cch = gc;
      }
      while (cch >= '0' && cch <= '9') {
      xx = (xx << 1) + (xx << 3) + (cch ^ '0'); cch = gc;
      }
      return xx * ff;
      }
      ll n, m, sq, mod;
      ll a[Maxn];
      ll pos[Maxn], l[400], r[400], sum[400], add[400], tot[400], mul[400];
      void pushdown(ll x) { //下传标记,把x所在的块的标记更新到块内每个元素上
      for(int i = l[pos[x]]; i <= r[pos[x]]; i++)
      a[i] = (a[i] * mul[pos[x]] + add[pos[x]]) % mod; //更新
      mul[pos[x]] = 1; add[pos[x]] = 0; //标记初始化
      }
      void modify1(ll x, ll y, ll num) { //乘法
      pushdown(x); //先把x所在块pushdown
      if(pos[x] == pos[y]) { //x,y在同一块内
      for(int i = x; i <= y; i++) { //更新
      sum[pos[i]] += a[i] * (num - 1);
      a[i] *= num; a[i] %= mod;
      }
      sum[pos[x]] %= mod;
      return ;
      }
      pushdown(y); //把y所在的块pushdown
      for(int i = pos[x] + 1; i <= pos[y] - 1; i++) { //整块修改
      sum[i] *= num; sum[i] %= mod;
      mul[i] *= num; mul[i] %= mod;
      add[i] *= num; add[i] %= mod; //注意乘法是加法标记也要维护
      }
      for(int i = x; i <= r[pos[x]]; i++) { //修改左边多出的块
      // cout << i << " ";
      sum[pos[i]] += (num - 1) * a[i];
      a[i] *= num; a[i] %= mod;
      }
      // cout << endl;
      sum[pos[x]] %= mod;
      for(int i = l[pos[y]]; i <= y; i++) { //修改右边多出的块
      // cout << i << " ";
      sum[pos[i]] += (num - 1) * a[i];
      a[i] *= num; a[i] %= mod;
      }
      // cout << endl;
      sum[pos[y]] %= mod;
      }
      void modify2(ll x, ll y, ll num) { //加法,同上
      pushdown(x);
      if(pos[x] == pos[y]) {
      for(int i = x; i <= y; i++) {
      a[i] += num, sum[pos[i]] += num;
      }
      sum[pos[x]] %= mod;
      return ;
      }
      pushdown(y);
      for(int i = pos[x] + 1; i <= pos[y] - 1; i++) {
      sum[i] += num * tot[i];
      add[i] += num;
      }
      for(int i = x; i <= r[pos[x]]; i++) {
      a[i] += num, sum[pos[i]] += num;
      }
      for(int i = l[pos[y]]; i <= y; i++) {
      a[i] += num, sum[pos[i]] += num;
      }
      sum[pos[x]] %= mod; sum[pos[y]] %= mod;
      }
      ll query(ll x, ll y) { //区间查询,基本含义同上
      ll res = 0;
      if(pos[x] == pos[y]) {
      for(int i = x; i <= y; i++)
      res += (a[i] * mul[pos[i]] + add[pos[i]]);
      return res;
      }
      for(int i = pos[x] + 1; i <= pos[y] - 1; i++)
      res += sum[i];
      for(int i = x; i <= r[pos[x]]; i++)
      res += (a[i] * mul[pos[i]] + add[pos[i]]);
      for(int i = l[pos[y]]; i <= y; i++)
      res += (a[i] * mul[pos[i]] + add[pos[i]]);
      return res % mod;
      }
      int main() {
      n = sc(); m = sc(); mod = sc();
      sq = (ll)sqrt(n);
      for(int i = 1; i <= n; i++) {
      a[i] = sc();
      pos[i] = (i - 1) / sq + 1; //用公式计算出第i个元素在第几个块
      if(!l[pos[i]]) l[pos[i]] = i; //如果左端点为标记,就标记上
      r[pos[i]] = i; //不断更新右端点,到最后一定正确
      tot[pos[i]]++; //元素个数++
      sum[pos[i]] += a[i]; //区间和维护
      mul[pos[i]] = 1; //乘法标记要初始化为1
      }
      while(m--) {
      ll flag = sc(), x = sc(), y = sc();
      if(flag == 1) {
      ll num = sc();
      modify1(x, y, num); //区间乘法
      }
      else if(flag == 2) {
      ll num = sc();
      modify2(x, y, num); //区间加法
      }
      else {
      printf("%lld\n", query(x, y)); //区间求和
      }
      }
      return 0;
      }
      // Coded by dy.

rp++