ACM Weekly 8 (待修改)

ACM Weekly 8

  • 涉及的知识点
    • 前缀和
      • 一维
      • 二维
    • 差分
      • 一维
      • 二维
    • RMQ问题(基于ST表)
    • 树状数组
    • 难题解析
    • 拓展
      • 线段树
    • 参考文献

涉及的知识点

第八周的练习主要涉及前缀和、差分、RMQ问题(基于ST表)、树状数组

拓展:线段树

前缀和

前缀和基本定义:一个数组某项下标之前(包括此元素)的所有数组元素和
前缀和在输入时便可以在线处理数据,之后在查询区间和时,可以很快地得到两个下标间的区间和。

一维

由定义, p r e [ n ] = ∑ i = 0 n v a l u e [ i ] pre[n]=\sum_{i=0}^nvalue[i] pre[n]=i=0nvalue[i],n为定义中的下标,
元素的递推式为 p r e [ n ] = p r e [ n − 1 ] + v a l u e [ n ] pre[n]=pre[n-1]+value[n] pre[n]=pre[n1]+value[n]

代码实现

int value[10000],pre[10000]
for(int i=1;i<=N;i++)
{
     
	cin >>value[i];
	pre[i]=pre[i-1]+value[i];
}
for(int i=1;i<N;i++)
	cout <<pre[i]<<" ";

二维

由定义, p r e [ n ] [ m ] = ∑ i = 0 n ∑ j = 0 m pre[n][m]=\sum_{i=0}^n\sum_{j=0}^m pre[n][m]=i=0nj=0m,n、m为定义中的下标
元素的递推式为 p r e [ n ] [ m ] = p r e [ n − 1 ] [ m ] + p r e [ n ] [ m − 1 ] − p r e [ n − 1 ] [ m − 1 ] + v a l u e [ n ] [ m ] pre[n][m]=pre[n-1][m]+pre[n][m-1]-pre[n-1][m-1]+value[n][m] pre[n][m]=pre[n1][m]+pre[n][m1]pre[n1][m1]+value[n][m]

代码实现

int value[10000][10000],pre[10000][10000];
for(int i=1;i<=N;i++)
	for(int j=1;j<=M;j++)
		{
     
			cin >>value[i][j];
			pre[i][j]=pre[i-1][j]+pre[i][j-1]-pre[i-1][j-1]+value[i][j];
		}
for(int i=1;i<=N;i++)
	for(int j=1;j<=M;j++)
		cout <<pre[i][j]<<" ";

差分

差分基本定义:对于一个数组中的某个元素,其差分为自身与先前一个元素的差,当然该元素不能为首个元素,定义首个元素的差分为1。

一维

由定义, d i f f e r [ i ] = v a l u e [ i ] − v a l u e [ i − 1 ] differ[i]=value[i]-value[i-1] differ[i]=value[i]value[i1]
对于一个一维序列进行区间操作,可以通过差分记录下每次操作,最后一次性进行所有操作,与前缀和结合,差分较难理解的便是操作时序列的首项+k(k为操作数,也可以为-k),末项的后一项-k(或+k)。

代码实现

int l,R,k;
cin >>l>>r>>k;
differ[l]+=k;
differ[r+1]-=k;
int add=0;
for(int i=1;i<=N;i++)
{
     
	add+=differ[i];//注意,当到达r+1时,add正好由k变为0
	value[i]+=value[i-1]+add;
}

二维

方法和一维类似,只不过需要记下四个位置对应的操作。

代码实现

while(m--)
{
     
	int x1,y1,x2,y2,k;
	cin >>x1>>y1>>x2>>y2>>k;
	differ[x1][y1]+=k;
	differ[x2+1][y2+1]+=k;
	differ[x2+1][y1]-=k;
	differ[x1][y2+1]-=k;
}

RMQ问题(基于ST表)

RMQ(Range Minimum/Maximum Query),即区间最值查询,该算法用较长时间预处理( O ( n log ⁡ n ) O(n\log n) O(nlogn)),之后在 O ( 1 ) O(1) O(1)内处理查询。

在RMQ算法中,使用一个二维数组 d p [ ] [ ] dp[ ][ ] dp[][]记录划分区间的最大/小值,在存储的时候采用二分的方法,即每次存储的是一个大区间的两个平分后的子区间,如 d p [ i ] [ j ] dp[i][j] dp[i][j]表示从序号i开始连续 2 j 2^j 2j个数的最小值。

d p [ i ] [ j ] dp[i][j] dp[i][j]时可以采用二分的方法,即求 d p [ i ] [ j − 1 ] dp[i][j-1] dp[i][j1] d p [ i + 2 j − 1 ] [ j − 1 ] dp[i+2^{j-1}][j-1] dp[i+2j1][j1]两个区间的最小值,前者为从 i → i + 2 j − 1 − 1 i\rightarrow i+2^{j-1}-1 ii+2j11,后者为 i + 2 j − 1 → i + 2 j − 1 i+2^{j-1}\rightarrow i+2^j-1 i+2j1i+2j1

那么,状态转移方程就可以写出:

d p [ i ] [ j ] = m i n ( d p [ i ] [ j − 1 ] , d p [ i + 1 < < ( j − 1 ) ] [ j − 1 ] ) dp[i][j]=min(dp[i][j-1],dp[i+1<<(j-1)][j-1]) dp[i][j]=min(dp[i][j1],dp[i+1<<(j1)][j1])

具体的代码实现如下:

void RMQ()
{
     
	for(int i=1;i<=N;i++)//初始化
		dp[i][0]=arr[i];
	for(int j=1;(1<<j)<=N;j++)//j取1、2、4...,步长
        for(int i=1;i+(1<<j)-1<=N;i++)
        //第一次更新0~1、1~2,之后j移位,第二次更新0~2,2~4,以此类推
            dp[i][j]=min(dp[i][j-1],dp[i+(1<<j-1)][j-1]);

树状数组

难题解析

拓展

线段树

线段树是一棵完美二叉树,树上每个节点维护一个区间,根维护整个区间,每个节点维护的是父节点的区间二分后的子区间之一,根据节点中维护的数据不同,线段树可提供不同功能,下面以RMQ为例

代码实现

#define MAX (1<<18)-1;
int N=1,dat[MAX];//存储线段树的全局数组
void init(int _N)//简单起见,将元素个数扩大到2的幂
{
     
	while(N<_N)N<<=1;
	for(int i=0;i<(N<<1)-1;i++)//设置所有值为INT_MAX
		dat[i]=INT_MAX;
}
void updata(int id,int a)
{
     
	id+=N-1;//叶结点
	dat[id]=a;
	while(id)//向上更新
	{
     
		id=(id-1)>>1;
		dat[id]=min(dat[(id<<1)+1],dat[(id<<1)+2]);
	}
}

参考文献

  1. 前缀和与差分
  2. 前缀和、二维前缀和与差分的小总结
  3. 前缀和、差分
  4. RMQ算法讲解
  5. 《挑战程序设计竞赛》

你可能感兴趣的:(ACM训练,算法,数据结构)