洛谷 P2357 守墓人
阿新 • • 發佈:2022-02-09
由題意得,需要區間修改,區間查詢
所以這個題可以用樹狀陣列,線段樹和分塊來做
咱們今天講分塊的做法:
先理解分塊,就會發現本題相當於板子題,整活
#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$,即每次取點值的時候要加上所在塊的懶惰數值
算是對分塊的基本瞭解了