树状数组维护区间最大值

树状数组维护区间最大值

1.关于树状数组

树状数组是一种支持单点修改区间查询的数据结构。普通树状数组维护的信息及运算要满足结合律可差分,如加法(和)、乘法(积)、异或等。 ——OI WIKI

顾名思义,树状数组,即数组中的每个元素都可以逻辑上对应为树上的一个节点,一棵子树的根节点保存整棵子树的信息,从而通过查询根节点而不是遍历一棵子树的方式减少运行时间。

具体而言,数组中下标为x的元素保存了下标\([x-lowbit(x)+1, x]\)区间内的信息。

以维护部分和为例,下面是一张OI WIKI的示意图:

可以看出:

1.下标为x的节点的父节点下标为\(x+lowbit(x)\)
2.下标为x的节点的子节点下标为\(x-2^0, x-2^1, ... x-lowbit(x)/2\)

若查询前x个元素的前缀和,已知c[x]保存了下标\([x-lowbit(x)+1, x]\)的区间和,如果\(x-lowbit(x) = 0\),则c[x]为前x个元素的前缀和;否则只需要接着查询前\(x-lowbit(x)\)元素的和,再加上c[x]即可。如此递归调用,可以证明时间复杂度为\(O(logN)\)

其他操作和应用详见这篇博客

以上文的思想,若想用树状数组维护区间最大值,可以尝试维护数组tr,使tr下标为x的元素保存了数组\(a[x-lowbit(x)+1, x]\)区间内的最大值。

2.建树

与维护部分和相同,每次用子节点更新父节点即可。时间复杂度为\(O(N)\)

void build()
{
    for(int i=1;i<=n;i++)
    {
        tr[i] = max(tr[i], a[i]);
        int j = i + lowbit(i);
        if (j <= n) tr[j] = max(tr[j], tr[i]);
    }
}

3.单点修改

首先用d修改a[x]和tr[x],然后用x的子节点更新tr[x],接着用tr[x]更新至x的父节点。如此递归直到超出数组范围。
时间复杂度为\(O(logN)\)

// 把a[x]修改为d
void upd(int x, int d)
{
    tr[x] = a[x] = d;
    for (int i=1; i<lowbit(x); i<<=1)
		tr[x] = max(tr[x], tr[x-i]);
 
  for(int i=x;i+lowbit(i)<=n;i+=lowbit(i))
  {
    int j = lowbit(i);
    tr[i+j] = max(tr[i+j], tr[i]); 
  }
}

4.区间查询

首先,最大值这个性质是不可差分的,即不能通过\([1, r]\), 和\([1, l]\)的最大值得出\([l,r]\)的最大值。
所以,我们只能在\(x-lowbit(x)+1 >= l\)时参考tr[x]。否则,若\(x=l\)可直接更新;若\(x>l\)需要用\([l,x-1]\)来更新。
可以证明:减一操作做最多做\(logN\)次,而每次减一都要伴随重新而来的\(logN\)次减\(lowbit\)操作,故时间复杂度为遗憾的\(O(logN)^2\)

int get(int l, int r)
{
	int res = -INF;
	while (l <= r)
	{
		for (; l <= r && r-lowbit(r)+1 >= l; r -= lowbit(r))
			res = max(tr[r], res);
		if(l > r) break;
		res = max(a[r], res);
		r --;
	}
	return res;
}

或者先减一,保证\(l<=r\)再用a[r]来更新。

int get(int l, int r)
{
	int ans = -INF;
	while (l <= r)
	{
		ans = max(a[r], ans);
		r --;
		// 注意判断条件为r-lowbit(r) >= l而不是r-lowbit(r)+1 >= l
		// 是因为要保证下一轮的r-lowbit(r) ==0时退出
		// 否则若l==1会死循环
		for (; r-lowbit(r) >= l; r -= lowbit(r))
			ans = max(tr[r], ans);
	}
	return ans;
}

虽然,指定区间查询的复杂度为\(O(logN)^2\),但假如是从数组第一个个元素开始查询,依然可以\(O(logN)\)内完成。代码如下:

int query(int x)//返回val[1]~val[x]中的最大值 
{
    int res=-INF;
    for(; x; x-=x&(-x))
        res=max(res,T[x]);
    return res;
}
posted @ 2023-05-29 13:46  DarkLights  阅读(425)  评论(0编辑  收藏  举报