<a class="link link--kukuri" data-letters="前缀和">前缀和</a>

1. 一维前缀和

公式:

f[i]=j=0ia[j]f[i]=\sum_{j=0}^ia[j]

每个计算都要循环一遍,太麻烦了!还有什么办法?

化简:f[i]=a[i]+f[i1]f[i]=a[i]+f[i-1]

例题:

给你一个长度为nn数列,问你mm个问题,分别是:第b[i]b[i]个数之前的每一个数相加,和为多少?

先将每一个数算出它的和。

1
2
3
4
for(int i=1;i<=10001;i++)
{
f[i]=a[i]+f[i-1];//化简公式
}

再解决询问的问题。

1
2
3
4
5
for(int i=1;i<=m;i++)
{
cin>>b;//输入
cout<<f[b];
}

2. 二维前缀和

如图,f[i][j]=f[i1][j]+f[i][j1]f[i][j]=f[i-1][j]+f[i][j-1],但是我们多算了绿块f[i1][j1]f[i-1][j-1],还少算了红块f[i][j]f[i][j],只要减掉,再加上,就可以啦 。

<a class="link link--kukuri" data-letters="线段树">线段树</a>

1.区间加

输入n,mn,m,分别表示有nn个数,m个步骤。

下面1个数x,表示原高度,然后m+2行操作。

操作1:给出L,R,yL,R,y ,表示在[L,R][L,R]区间内,每个数加上y。

操作2:给出L,RL,R,表示询问在[L,R][L,R]区间内每个高度之和。

操作1表示为11 LL RR xx

操作2表示为22 LL RR

方案一:将[L,R][L,R]区间内,每个数循环加上y。但是复杂度太高。

方案二:将a[L]=x+ya[L]=x+ya[R+1]=ya[R+1]-=y

然后再用循环a[i]=a[i1]a[i]=a[i-1]

这样复杂度就大大减少啦。

2.区间乘

代码如下,方法与区间加similar:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
void cheng(long long l,long long r,long long id,long long tl,long long tr,long long x)
{
if(tl<=l&&r<=tr)
{
ch[id]=((long long)ch[id]*x)%P;
dx[id]=((long long)dx[id]*x)%P;
sum[id]=((long long)sum[id]*x)%P;
return;
}
int mid=(l+r)/2;
if((ch[id]!=1) || dx[id])
{
dx[id<<1]=((long long)dx[id<<1]*ch[id]+dx[id])%P;
ch[id<<1]=((long long)ch[id<<1]*ch[id])%P;
sum[id<<1]=(((long long)sum[id<<1]*ch[id])%P+(LL)dx[id]*(mid-l+1)%P)%P;

ch[id<<1|1]=((long long)ch[id<<1|1]*ch[id])%P;dx[id<<1|1]=((LL)dx[id<<1|1]*ch[id]+dx[id])%P;
sum[id<<1|1]=(((long long)sum[id<<1|1]*ch[id])%P+(LL)dx[id]*(r-mid)%P)%P;

ch[id]=1;dx[id]=0;
}

if(tl<=mid) cheng(l,mid,id*2,tl,tr,x);
if(tr>=mid+1) cheng(mid+1,r,id*2+1,tl,tr,x);
sum[id]=(sum[id<<1]+sum[id<<1|1])%P;
return;
}

<a class="link link--kukuri" data-letters="记忆化搜索">记忆化搜索</a>

不论是前缀和还是dpdp,还是……

大部分算法都要有动态转移方程。

但是动态转移方程有个缺陷:

有很多动态方程会重复计算好几遍,这会使复杂度大大增加。

但用什么方法来解决此问题呢?

有些人想到了:用一个数组储存函数值。

这就是记忆化搜索。

该算法使用范围:当一个题目根据具体判断可能会出现重复的答案时候,或是不加以优化的暴力搜索会超时的时候,就可以考虑使用记忆化搜索。

e.g.斐波那契数列。

未经过优化的代码:

1
2
3
4
5
int f(int n)
{
if(n==1||n==2) return 1;
else f(n-1)+f(n-2);
}


费时大,许多被重复计算,要算12次。

优化代码:

1
2
3
4
5
6
7
8
9
10
long long a[10001];
int f(int n)
{
if(n==1||n==2) return 1;
if(a[n]==0)
{
return a[n]=f(n-1)+f(n-2);
}
else return a[n];
}

用一个数组储存f(n)f(n)的值,遇到则输出,不然赋值,只需5次,复杂度O(n)O(n)

可以看出用了记忆化搜索,TLE不用愁!

<a class="link link--kukuri" data-letters="应用">应用</a>

1.前缀和

P1147 连续自然数和\texttt{\color{brown}{P1147 连续自然数和}}

P1387 最大正方形\texttt{\color{orange}{P1387 最大正方形}}

P1437 [HNOI2004]敲砖块\texttt{\color{blue}{P1437 [HNOI2004]敲砖块}}

2.线段树

U121358 在火星上\texttt{\color{orange}{U121358 在火星上}}

P1880 [NOI1995]石子合并\texttt{\color{green}{P1880 [NOI1995]石子合并}}

P1437 [P4170 [CQOI2007]涂色\texttt{\color{blue}{P1437 [P4170 [CQOI2007]涂色}}

3.记忆化搜索

P1464 Function\texttt{\color{brown}{P1464 Function}}

P1352 没有上司的舞会\texttt{\color{orange}{P1352 没有上司的舞会}}

U121357 找回病人\texttt{\color{green}{U121357 找回病人}}