【SPOJ GSS3】Can you answer these queries III

传送门


problem

给出 n n n 个数 a i a_i ai,有 q q q 次操作,操作分为以下两种:

  • 0    x    y 0 \;x \;y 0xy:把 a x a_x ax 修改为 y y y
  • 1    l    r 1\; l\; r 1lr:询问区间 [ l , r ] [l, r] [l,r] 的最大子段和。

数据范围: n , q ≤ 50000 n,q \le 50000 n,q50000


solution

这道题首先有一个线段树的经典做法,即维护最大前缀和最大后缀和最大子段和以及这段区间的和,这里不再赘述。

这篇题解是从 动态DP 的角度来做这道题的。关于动态DP,可以参考这篇博客,感觉写得非常不错。

f i f_i fi 表示以 a i a_i ai 结尾的最大子段和, g i g_i gi 表示 [ 1 , i ] [1,i] [1,i] 的最大子段和,那么有转移:

f i = max ⁡ { f i − 1 + a i , a i } g i = max ⁡ { g i − 1 , f i } = max ⁡ { g i − 1 , f i − 1 + a i , a i } \begin{aligned} f_i&=\max\{f_{i-1}+a_i,a_i\}\\ g_i&=\max\{g_{i-1},f_i\}=\max\{g_{i-1},f_{i-1}+a_i,a_i\} \end{aligned} figi=max{fi1+ai,ai}=max{gi1,fi}=max{gi1,fi1+ai,ai}

我们新定义一种矩阵乘法,然后把上面的转移写成矩阵乘法的形式:

[ a i − ∞ a i a i 0 a i − ∞ − ∞ 0 ] [ f i − 1 g i − 1 0 ] = [ f i g i 0 ] \begin{bmatrix} a_i&-\infty&a_i\\ a_i&0&a_i\\ -\infty&-\infty&0\end{bmatrix} \begin{bmatrix}f_{i-1}\\g_{i-1}\\0\end{bmatrix}= \begin{bmatrix}f_i\\g_i\\0\end{bmatrix} aiai0aiai0fi1gi10=figi0

具体的定义方式还是参考那篇博客吧。

那么每一个点就可以改写成一个矩阵,用线段树维护矩阵的乘法即可,修改也十分方便。

时间复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn)


code

#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 50005
#define inf (1<<30)
using namespace std;
int n,q;
int val[N];
struct matrix{
	int m[3][3];
	matrix()  {memset(m,128,sizeof(m));}
	friend matrix operator*(const matrix &A,const matrix &B){
		matrix C;
		for(int i=0;i<=2;++i)
			for(int k=0;k<=2;++k)
				for(int j=0;j<=2;++j)
					C.m[i][j]=max(C.m[i][j],A.m[i][k]+B.m[k][j]);
		return C;
	}
}Seg[N<<2];
void build(int root,int l,int r){
	if(l==r){
		Seg[root].m[0][1]=Seg[root].m[2][0]=Seg[root].m[2][1]=-inf;
		Seg[root].m[0][0]=Seg[root].m[0][2]=Seg[root].m[1][0]=Seg[root].m[1][2]=val[l];
		Seg[root].m[1][1]=Seg[root].m[2][2]=0;
		return;
	}
	int mid=(l+r)>>1;
	build(root<<1,l,mid),build(root<<1|1,mid+1,r);
	Seg[root]=Seg[root<<1]*Seg[root<<1|1];
}
void Modify(int root,int l,int r,int x,int y){
	if(l==r){
		Seg[root].m[0][0]=Seg[root].m[0][2]=Seg[root].m[1][0]=Seg[root].m[1][2]=y;
		return;
	}
	int mid=(l+r)>>1;
	if(x<=mid)  Modify(root<<1,l,mid,x,y);
	else  Modify(root<<1|1,mid+1,r,x,y);
	Seg[root]=Seg[root<<1]*Seg[root<<1|1];
}
matrix Query(int root,int l,int r,int x,int y){
	if(l>=x&&r<=y)  return Seg[root];
	int mid=(l+r)>>1;
	if(y<=mid)  return Query(root<<1,l,mid,x,y);
	if(x>mid)  return Query(root<<1|1,mid+1,r,x,y);
	return Query(root<<1,l,mid,x,y)*Query(root<<1|1,mid+1,r,x,y);
}
int main(){
	int op,x,y;
	scanf("%d",&n);
	for(int i=1;i<=n;++i)  scanf("%d",&val[i]);
	build(1,1,n);
	scanf("%d",&q);
	for(int i=1;i<=q;++i){
		scanf("%d%d%d",&op,&x,&y);
		if(op){
			matrix ans=Query(1,1,n,x,y);
			printf("%d\n",max(ans.m[1][0],ans.m[1][2]));
		}
		else  Modify(1,1,n,x,y);
	}
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值