前缀和优化

来源:互联网 发布:智能算法优化 编辑:程序博客网 时间:2024/06/07 15:21

在了解之前,先来阐明几个基础概念:


1.时间复杂度O(n):时间复杂度在算法中是一个重要概念,此处O为微积分概念,不做详解。我们可以简记一重循化时间复杂度即为O(n),二重循环时间复杂度为O(n^2),顺推三重循环即为O(n^3)。
e.g.

for(i=1;i<n;i++)    for (j=1;j<m;j++)        ……

该程序时间复杂度为O(n*m);
这里再附一篇文章进一步帮助了解http://m.blog.csdn.net/firefly_2002/article/details/8008987
2.区间和:即某一区间内所有有元素的和。
e.g.
无序序列:4 5 3 1 9 每个元素的位置记为1 2 3 4 5,此时[2,4]区间和为5+3+1=9。


♢一维前缀和:
先来看这样一个问题:请求出101-1852区间内3的倍数的数的个数。这里我们可以先考虑计算一下1-1852区间内3的倍数的个数再计算1-101区间内3的倍数的个数,相减得到结果,看起来很靠谱的样子,我们可以计算一下:1852|3-101|3=583(“|”是整除的意思),貌似没错,那请继续看下面的例子:计算区间3-12间3的倍数的个数,我们还用上面的方法计算12|3-3|3=3,很明显这一区间内有四个数都是3的倍数,那么问题出在了哪里? 问题出在端点上,如何解决?我们可以对左区间-1处理。

再举个栗子,我们仍以3的倍数为例计算,如果区间为[6-9],那么这个区间中3的倍数的数的个数为2(6和9),这一结论与我们的公式是重合的,可以发现对左区间-1对结果并无影响。再来看,如果我们将左区间数减去2,好像仍然对结果无影响,但是你大可以仔细想想就能轻松举出反例(如区间[7-9],我们理想中的结果是1,但我们计算却发现结果是2),同理-3更无法成立。

好的给最终公式:r/k-(l-1)/k k即为所求的基数
下面我们就来具体讲解:

首先我们要引入一个f数组,这个数组有用么,当然。f数组存储了1+2+3+…….+n的大小,即1—n的所有数的和,这就是我们的主题,前缀和。比如f[4]=1+2+3+4=10; 知道了这个概念后我们先考虑一个问题:区间[101,1001]的区间和是多少?这时我们就应该返回到我们的第一个问题,是否可以先求解[1,1001]的区间和,再求出[1-101]的区间和,相减得到结论

#include <iostream>#include <cstdio>using namespace std;int main(){   int sum=0;   for(int i=101;i<=1001;i++){      sum+=i;   }   cout<<sum;   return 0;}

这样当然可以但是应用起来不灵活
所以前缀和的思路:
f([0,1])=0+1
f([0,2])=0+1+2
f([0,3])=0+1+2+3
f([0,4])=0+1+2+3+4
对比一下可以发现随着右半区间数的递增,f数组的值不断的加右区间新数,而右区间以左的数较上一区间却未变。公式:f[n]=f[n-1]+a[n] (a[n]指代新数)
然后就很好懂了啊,代码:

#include <iostream>#include <cstring>using namespace std;int f[10001],a[10001]; int main(){    int left,right;         //定义区间左右端点    cin>>left>>right;       //求某区间的区间和     memset(f,0,sizeof(f));  //数组清空     //先求出1-1001各数的前缀和 (预处理)    for(int i=1;i<=right;i++)        f[i]=f[i-1]+i;            //这里将序列定为单调递增序列,所以用i代a[i],择情况而视      cout<<f[right]-f[left-1];    //输出区间和     return 0;} //预处理复杂度为O(n),查询复杂度为O(1)

接下来我们进一步探讨二维的前缀和,也就是在平面内处理矩阵的问题

二维前缀和:
一维前缀和解决的是线性问题,也就是在一维状态下解决区间和问题,那么二维前缀和就是在平面内解决区间和问题,这里我们要再引入一个概念:矩阵。
这里我们需要了解的矩阵只是一个用数字填充的图的概念,如图即为一个3x3的数字矩阵:
1 2 3
4 5 6
7 8 9


那么二维的的前缀和有什么用呢?
毫无疑问仍然是在解决区间和问题的应用,矩阵区间和?其实很简单,矩阵的区间和就是指某矩阵右下端点
至该矩阵左上端点的矩阵的所有元素的和(这里我们直接用矩阵右下端点来表示一个以右下端点到点(1,1)的矩阵).
e.g.
矩阵
14 6 54
3 3 9
8 12 24
中(2,2)-(3,3)矩阵的矩阵区间和为3+9+12+24;
很简单吧(确实是这样),那么如果我们把这个矩阵扩展为10000*10000大小的矩阵,再去求矩阵区间和,不由得????好吧,我们还是返回解释一维区间和的问题,类比一下,好像有了点思路,我们貌似可以先求出(3,3)的矩阵大小再减去(2,2)的矩阵大小,(jiang信jiang疑),但这是有问题的,画图求知:

如图(3,3)矩阵减去(2,2)矩阵得到的是黑色与橘色部分的和而非我们期待中的橘色部分。
那么如何解决呢,我们应该换个思路
这里写图片描述

这里我们就能够用到小学数学中的容斥原理:
S(4)=S(1+2+3+4)-S(1+3)-S(1+2)+S(1);

所以我们只要求解矩阵1+3,1+2,1,1+2+3+4的大小相减即可得到矩阵区间和,同样的我们引入二维f数组,代表某端点至(1,1)的矩阵大小:
e.g.有一个5x5的矩阵,请输入该矩阵的各个元素,并输入要求解的矩阵区间,上代码:

#include <iostream>#include <cstdio>using namespace std;int f[101][101],a[10][10];int main(){    for(int i=1;i<=5;i++){      for(int j=1;j<=5;j++){         cin>>a[i][j];         f[i][j]=f[i-1][j]+f[i][j-1]+a[i][j];      }    }    int x,x1,y,y1;    cin>>x>>y>>x2>>y2;    cout<<f[x2][y2]-f[x2][y-1]-f[x-1[y2]+f[x-1][y-1];    return 0;}

注意这里x-1,y-1是为了包含该矩阵边界