前缀和与差分
1、二维差分2、二维差分入门练习—P3397 地毯3、一维差分练习—P3406 海底高铁4、差分思想优化—序列查询新解(CSP杂题)reference
1、二维差分一维差分时是在需要更新的位置开始,不需要的位置结束,而二维的也一样。如果我们要在左上角是 (x1, y1),右下角是(x2,y2)的矩形区间每个值都+a,我们要在区间开始位置(x1,y1)处+a,根据前缀和的性质,那么它影响的就是整个黄色部分,多影响了两个蓝色部分,所以在两个蓝色部分-a消除+a的影响,而两个蓝色部分重叠的绿色部分多受了个-a的影响,所以绿色部分+a消除影响。
2、二维差分入门练习—P3397 地毯P3397 地毯 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
暴力可以做,复杂度n3勉强满足n,m <= 1000,使用二维前缀和和二维差分后,复杂度降至O(n2)。差分与前缀和互为逆运算,一维差分使用O(1)复杂度表示O(n)的覆盖,而二维差分使用O(1)的复杂度表示O(n2)的覆盖
#include3、一维差分练习—P3406 海底高铁#include using namespace std; long long m,n,a[1010][1010]; //直接模拟 void solve1(){ cin>>n>>m; long long x1,y1,x2,y2; for(long long i=0;i >x1>>y1>>x2>>y2; for(long long j=x1;j<=x2;j++){ for(long long k=y1;k<=y2;k++) a[j][k]++; } } for(long long i=1;i<=n;i++){ for(long long j=1;j<=n;j++) printf("%lld ",a[i][j]); printf("n"); } } //二维差分 void solve2(){ cin>>n>>m; long long x1,x2,y1,y2; for(long long i=1;i<=m;i++){ cin>>x1>>y1>>x2>>y2; a[x1][y1]++; a[x1][y2+1]--; a[x2+1][y1]--; a[x2+1][y2+1]++; } //逆向还原(求前缀和) for(long long i=1;i<=n;i++){ for(long long j=1;j<=n;j++){ a[i][j]+=a[i-1][j]+a[i][j-1]-a[i-1][j-1]; printf("%lld ",a[i][j]); } printf("n"); } } int main() { solve2(); system("pause"); return 0; }
P3406 海底高铁 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
这道题是一维前缀和/一维差分,采用差分思想O(m)读入,使用前缀和获取覆盖并进行方案选择花费O(n)。总的时间复杂度O(m+n)可以把前缀和与选择方案合二为一
#include#include using namespace std; long long n,m,A,B,C,cover[100100]; long long dist=0; void solve1(){ freopen("a.in","r",stdin); freopen("a.out","w",stdout); scanf("%lld%lld",&n,&m); long long startx, endx; cin >> startx; //一维差分输入 for(long long j = 2; j <= m; j++){ scanf("%lld",&endx); if(startx < endx){ cover[startx]++; cover[endx]--; } else{ cover[endx]++; cover[startx]--; } //Swap(startx,endx); startx = endx; } //前缀和求覆盖+选择 for(long long i = 1; i < n; i++){ cover[i] += cover[i - 1]; scanf("%lld%lld%lld",&A,&B,&C); dist += A*cover[i] < B*cover[i] + C ? A*cover[i] : B*cover[i] + C; } } int main() { solve1(); //system("pause"); cout< 教训:
1、long long和int 不要混用,或许会带来精度的损失导致出错?这道题混用的记录如下记录详情 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
2、理解清楚题意再做,有些情况下没有AC并不是算法问题,可能是题目中的某一小步理解错误,比如这道题“从城市 P1出发分别按照P1,P2,P3…PM 的顺序访问各个城市”被我理解成“从城市1出发…”导致与最终结果总相差一个常数
3、长时间WR看答案后认为算法没问题就要仔细再读题,仔细看题解找不同。
4、差分思想优化—序列查询新解(CSP杂题)计算机软件能力认证考试系统
注意到N在109量级,而n在105直接模拟会超时得到70分
思想:将0—N的模拟转化为0—n的等差数列求和。具体是按照这n个数进行分组,然后按照r个等差数列求和,最后减去多求的部分(代码中error -= fabs(first) * A_begin_rem + fabs(last) *A_end_rem)
#include#include using namespace std; long long func(long long first, long long last, long long r) { long long result; if (first * last >= 0) { result=fabs(first + last) * (fabs(last - first) + 1) / 2 * r; } else { result = (fabs(first) + 1) * fabs(first) / 2 * r + (last + 1) * last / 2 * r; } return result; } int main() { int n, N; cin >> n >> N; long long r = N / (n + 1); long long A[n]={0}; for (int i = 1; i <= n; ++i) { cin >> A[i]; } A[n+1]=N; long long error = 0; for (int i = 0; i <= n; ++i) { long long begin_num = A[i] / r; long long end_num = (A[i + 1] - 1) / r; long long first = begin_num - i, last = end_num - i; error += func(first, last, r); long long A_bebin_rem = A[i] % r; long long A_end_rem = r - (A[i + 1] - 1) % r - 1; error -= fabs(first) * A_begin_rem + fabs(last) * A_end_rem; } cout << error << endl; //system("pause"); return 0; } 教训:
1、中间变量命名规范,便于阅读
2、写程序之前可以先在纸上演算,确定思路正确再写代码
3、适当空格、空行使代码看起来清晰
reference二维差分 - 新之守护者 - 博客园 (cnblogs.com)



