洛谷 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了,没明白错在哪
2.9更新:今天问了老师,意识到问题应该是出在区间修改查询的时候没有判断起点终点在同一个块内
网上正解:
#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$,即每次取点值的时候要加上所在块的懒惰数值
- 正如2.8时的问题,注意判断起点终点在同一个块内的情况
- 处理左碎块时要注意$for$最后要取$min$
- 两个函数的基本构造完全一样!
以上算是对分块的基本了解了