2019.03.24【SPOJ-GSS1】Can you answer these queries I(猫树)

传送门


解析:

对于这种问题,如果不考虑修改的话可能会有一些做法通过预处理来降低询问复杂度

而其中一种通过增加预处理复杂度来降低询问复杂度的做法我们称之为猫树(出自猫锟的博客

对于不带修改的区间最大子段和问题,利用猫树可以做到 O ( n log ⁡ n ) O(n \log n) O(nlogn)的预处理和 O ( 1 ) O(1) O(1)回答每个询问。

猫树其实是一个类似线段树的结构,其实这道题上猫树的应用就有点像离线分治了。

不过猫树可以做到强制在线。

于是就可以把询问开到 1 e 7 1e7 1e7出毒瘤题了

设当前询问区间为 l , r l,r l,r
首先假设我们找到了一个节点, l , r l,r l,r是它的子区间,且 l , r l,r l,r分别在它不同的子节点中。

可以证明,这样的节点是唯一的。假设这一个节点的中点为 m i d mid mid

利用DP求最大子段和的方式,我们可以 O ( n ) O(n) O(n)处理出 [ l , m i d ] [l,mid] [l,mid]的最大子段和和最大后缀和, [ m i d + 1 , r ] [mid+1,r] [mid+1,r]的最大子段和和最大前缀和。显然答案要么在 [ l , m i d ] [l,mid] [l,mid]要么在 [ m i d + 1 , r ] [mid+1,r] [mid+1,r]要么就是横跨,三个拿来比较一下就行了。

所以问题是满足条件的区间怎么找。

显然是 l , r l,r l,r对应叶子节点在猫树上的LCA,强行将猫树大小转为 2 k 2^k 2k,预处理 l o g log log数组就可以 O ( 1 ) O(1) O(1)找到LCA了。


代码:

#include<bits/stdc++.h>
#define ll long long
#define gc get_char
#define re register
#define cs const

namespace IO{
	inline char get_char(){
		static cs int Rlen=1<<20|1;
		static char buf[Rlen],*p1,*p2;
		return (p1==p2)&&(p2=(p1=buf)+fread(buf,1,Rlen,stdin),p1==p2)?EOF:*p1++;
	}
	
	inline int getint(){
		re char c;
		re bool f=0;
		while(!isdigit(c=gc()))f^=c=='-';re int num=c^48;
		while(isdigit(c=gc()))num=(num+(num<<2)<<1)+(c^48);
		return f?-num:num;
	}
}
using namespace IO;

inline int max(int a,int b){return a>b?a:b;}

cs int N=1<<19|7;

int n,m,a[N],logn[N],pos[N];
int p[21][N],s[21][N];

inline void build(int k,int l,int r,int dep){
	if(l==r){
		pos[l]=k;
		return ;
	}
	int mid=(l+r)>>1;
	build(k<<1,l,mid,dep+1);
	build(k<<1|1,mid+1,r,dep+1);
	int pre,sum;
	p[dep][mid]=s[dep][mid]=pre=sum=a[mid];
	if(sum<0)sum=0;
	for(int re i=mid-1;i>=l;--i){
		pre+=a[i],sum+=a[i];
		s[dep][i]=max(s[dep][i+1],pre);
		p[dep][i]=max(p[dep][i+1],sum);
		if(sum<0)sum=0; 
	}
	p[dep][mid+1]=s[dep][mid+1]=pre=sum=a[mid+1];
	if(sum<0)sum=0;
	for(int re i=mid+2;i<=r;++i){
		pre+=a[i],sum+=a[i];
		s[dep][i]=max(s[dep][i-1],pre);
		p[dep][i]=max(p[dep][i-1],sum);
		if(sum<0)sum=0;
	}
}

inline int query(int l,int r){
	if(l==r)return a[l];
	int dep=logn[pos[l]]-logn[pos[l]^pos[r]];
	return max(max(p[dep][l],p[dep][r]),s[dep][l]+s[dep][r]);
}

signed main(){
	n=getint();
	for(int re i=1;i<=n;++i)a[i]=getint();
	int len=1;
	while(len<n)len<<=1;
	build(1,1,len,1);
	for(int re i=2;i<=(len<<1);++i)logn[i]=logn[i>>1]+1;
	m=getint();
	while(m--){
		int l=getint(),r=getint();
		std::cout<<query(l,r)<<"\n";
	}
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值