1. 程式人生 > 其它 >洛谷 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了,沒明白錯在哪

網上正解

#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$,即每次取點值的時候要加上所在塊的懶惰數值

算是對分塊的基本瞭解了