洛谷 P2357 守墓人

题干

由题意得,需要区间修改,区间查询

所以这个题可以用树状数组,线段树和分块来做

咱们今天讲分块的做法:

先理解分块,就会发现本题相当于板子题,整活

洛谷 P2357 守墓人
#include<iostream>
#include<cstdio>
#include<cmath>
#define NUM 200010
using namespace std;
struct dian{
    int k;
    long long zhi;
};
dian a[NUM];//存下每个点当前的数值zhi以及所在块的编号 
struct kuai{
    long long sum,tag;
    int l,r;
};
kuai b[1000];//存下每个块的范围(l-r),懒惰标记与块内总和 
int n,m;//点数量与询问数量 
int main(){
    cin >> n >> m;
    int w = (int)sqrt(n),k = n/w;//w是块的数量,k是每个满块的零点数量 
    if( w*w < n ) w++;//凑满 
    if( n == 3 ){w = 2;k = 2;}//特殊处理 
    if( n == 2 ){w = 2;k = 1;}
//    cout << "块的数量为:" << w << "  每个块里有数量为:" << k << endl; 
    int cnt = 0;
    for( int i = 1;i <= n;i++ )
        cin >> a[i].zhi;
    for( int i = 1;i <= w;i++ ){
        for( int j = 1;j <= k;j++ ){
            a[++cnt].k = i;
            if( j == 1 ) b[i].l = cnt;//如果是新块的开头,说明l为该点 
            b[i].r = cnt;
            b[i].sum += a[cnt].zhi; 
            if( cnt >= n ) break;//所有块都已经就位 
        }
    }
    for( int i = 1;i <= w;i++ )
//        printf( "i = %d,sum = %d,l = %d,r = %d\n",i,b[i].sum,b[i].l,b[i].r );
    int l,r,p;
    for( int fi = 1;fi <= m;fi++ ){
        cin >> cnt;
        if( cnt == 1 ){
            cin >> l >> r >> p;
            bool ok = 0;
            for( int i = 1;i <= w;i++ ){ //枚举每个块 
                if( ok ) break;
                if( l <= b[i].l && b[i].r <= r ) b[i].tag += p;//这个块完全在范围中 
                else{
                    if( l <= b[i].r && b[i].l < l ) //是左边的碎块 
                        for( int j = l;j <= min(r,b[i].r);j++ ){
                            a[j].zhi += p;
                            b[a[j].k].sum += p;
                        }
                    if( b[i].l <= r && b[i].r > r ){ //右边的碎块 
                        ok = 1;//已经到头了 
                        for( int j = max(l,b[i].l);j <= r;j++ ){
                            a[j].zhi += p;
                            b[a[j].k].sum += p;
                        }
                    }
                }
            }
//            cout << "进行了区间修改:\n";
            for( int i = 1;i <= n;i++ )
                cout << a[i].zhi + b[a[i].k].tag << " ";
            cout << endl;
        }
        if( cnt == 2 ){
            cin >> p;
            a[1].zhi += p;
            b[1].sum += p;
//            cout << "当前主坟为:" << a[1].zhi+b[1].tag << endl;
            continue;
        }
        if( cnt == 3 ){
            cin >> p;
            a[1].zhi -= p;
            b[1].sum -= p;
//            cout << "当前主坟为:" << a[1].zhi+b[1].tag << endl;
            continue;
        }
        if( cnt == 4 ){
//            cout << "各个区间取和:" << endl;
            long long ans = 0;bool ok = 0;
            cin >> l >> r;
            for( int i = 1;i <= w;i++ ){
                if( ok ) break;
                if( l <= b[i].l && b[i].r <= r ){
                    ans += b[i].sum;
                    ans += (b[i].r-b[i].l+1) * b[i].tag;
                }
                else{
                    if( l <= b[i].r && b[i].l < l )
                        for( int j = l;j <= min(r,b[i].r);j++ ){
                            ans += a[j].zhi + b[a[j].k].tag; //当前每个数的数值加所在块的懒惰标记 
                        }
                    if( b[i].l <= r && b[i].r > r ){
                        ok = 1;
                        for( int j = max(l,b[i].l);j <= r;j++ )
                            ans += a[j].zhi + b[a[j].k].tag;
                    }
                }
//                cout << ans << " ";
            }
//            cout << endl << "区间和为:";
            cout << ans << endl;
            continue;
        }
        if( cnt == 5 ){
//            cout << "主坟墓为:";
            cout << a[1].zhi+b[1].tag << endl;
            continue;
        }
    }
    
    
    return 0;
}
我的理解

然而我的理解WA了,没明白错在哪

网上正解

洛谷 P2357 守墓人
#include<iostream>
#include<cstdio>
#include<cmath>
#define NUM 200010
using namespace std;
struct dian{
    int k;
    long long zhi;
};
dian a[NUM];
struct kuai{
    long long sum,tag;
    int l,r;
};
kuai b[1000];
int n,m;
int main(){
    cin >> n >> m;
    int w = (int)sqrt(n);//就是每个块的大小 
    cout << w << endl;
    for( int i = 1;i <= n;i++ ){
        cin >> a[i].zhi;
        a[i].k = (i-1) / w + 1;//每个点在哪个块,存下 
        b[a[i].k].sum += a[i].zhi;
    }
    cout << endl;
    for( int i = 1;i <= n;i++ )
        cout << a[i].k << " ";
    cout << endl;
    int l,r,p,cnt;
    for( int fi = 1;fi <= m;fi++ ){ //开始操作 
        cin >> cnt;
        if( cnt == 1 ){
            cin >> l >> r >> p;
            //add(l,r,q)__________________________ 
          / for( int i = l;i <= min(r,a[l].k*w);i++ ){ //处理左边碎块
         /  //从最左端点开始,一直到a[l].k*w,就是第k个块的结尾的点编号 
        /       a[i].zhi += p;
       /        b[a[i].k].sum += p;
      /     }
     /      if( a[l].k != a[r].k ){ //如果不止在一个块里 
    /         for( int i = (a[r].k-1)*w+1;i <= r;i++ ) //处理右边碎块 
    \         //从前结尾前一个块的最后+1(即结尾所在块的第一个)开始,一直到 
     \          a[i].zhi += p,b[a[i].k].sum += p;//碎块本身加,所在大块加 
      \     }
       \    //处理范围所包含的整块
        \    //开始于起点所在的碎块的后一个块(即第一个整块),结束于终点的前一个块(最后一个整块) 
         \  for( int i = a[l].k+1;i <= a[r].k-1;i++ ) b[i].tag += p;
//        \ ____________________________________
        }
        if( cnt == 2 ){
            cin >> p;
            a[1].zhi += p;
            b[1].sum += p;
            continue;
        }
        if( cnt == 3 ){
            cin >> p;
            a[1].zhi -= p;
            b[1].sum -= p;
            continue;
        }
        if( cnt == 4 ){
            long long ans = 0;bool ok = 0;
            cin >> l >> r;
            //query(l,r) ,原理类似于上面的add函数 
            for( int i = l;i <= min(r,a[l].k * w);i++ )
                ans += a[i].zhi + b[a[i].k].tag;
            if( a[l].k != a[r].k ){
                for( int i = (a[r].k-1)*w+1;i <= r;i++ )
                    ans += a[i].zhi + b[a[i].k].tag;
            }
            for( int i = a[l].k+1;i < a[r].k;i++ )
                ans += b[i].sum + b[i].tag * w;
            cout << ans << endl;
            continue;
        }
        if( cnt == 5 ){
            cout << a[1].zhi+b[1].tag << endl;
            continue;
        }
    }
    return 0;
}
网上正解

要注意每次使用真实的$a[i].zhi$时一点要加上$b[a[i].k].tag$,即每次取点值的时候要加上所在块的懒惰数值

算是对分块的基本了解了

上一篇:leetcode2181. 合并零之间的节点(mid)(281)


下一篇:「学习笔记」尺取法