由题意得,需要区间修改,区间查询
所以这个题可以用树状数组,线段树和分块来做
咱们今天讲分块的做法:
先理解分块,就会发现本题相当于板子题,整活
#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$,即每次取点值的时候要加上所在块的懒惰数值
算是对分块的基本了解了