P1725 琪露诺题解 单调队列维护dp + Can you answer these queries III题解 线段树

P1725 琪露诺

本题有明显的阶段和状态,可以用线性dp来做。

设 f[i] 为: 到达位置 i 时最大的价值和 , 则状态转移方程如下 :
f[i]=max⁡(f[j])+A[i]  (i−R≤j≤i−L)

但是本题的N≤2e5,因此不能直接暴力枚举f[j],由于发现L和R是固定的,i-R和i-L即为一个固定的范围,因此考虑使用单调队列维护这个范围中的max⁡(f[j])。

按上述转移方程开始递推。

最后再从左往右扫描一遍寻找最大的f[i]即可。

​​​我使用的是数组模拟队列,如果你不会的单调队列的话,建议先查阅相应文章。

总之单调队列就是说这个队列中的数要么从小到大排列,要么从大到小排列。

而我们在此处使用的是从大到小的单调队列。

AC代码(注意一些小细节)

#include<bits/stdc++.h>
using namespace std;
int n,l,r,maxn;
int q[200010];
int a[200010],f[200010];
int main()
{
	maxn=-1e9;
    cin>>n>>l>>r;
	for(int i=2;i<=l+1;i++)
	{
		f[i]=-1e9;
	}
    int h=1,t=0;
    for(int i=1;i<=n+1;i++){
    	scanf("%d",&a[i]);
	} 
	if(l==r){
		int ans=0;
		for(int i=1;i<=n+1;i+=l){
			ans+=a[i];
		}
		cout<<ans<<endl;
		return 0;
	}
	for(int i=1;i<=(n-l+1);i++){
		int j=i+l;
		while(h<=t && i-q[h]>=(r-l+1)) h++;
	    while(h<=t && f[i]>f[q[t]]) t--;
	    q[++t]=i;
	    f[j]=f[q[h]]+a[j];
	}
	for(int i=1;i<=n;i++){
		maxn=max(maxn,f[i]);
	}
	cout<<maxn;
    return 0;
}

GSS3 - Can you answer these queries III

思路借用 SilentEAG 大佬 与 进阶指南上的讲解。但我的代码更为通俗易懂。

需要你提供一种数据结构使之能够查询区间最大连续子段和,并且支持单点修改。

让我们来分析一下,在区间上进行操作自然而然可以想到树状数组或者是线段树。这里单点修改好办,难就难在查询上。

想一想怎么办?或者说,我们如何整理子区间的信息?

仔细思考后,我们会发现,一个区间上的连续最大和只有几种情况:

  1. 连续最大和的区间只在左儿子所对应的区间上。
  2. 连续最大和的区间只在右儿子所对应的区间上。
  3. 连续最大和的区间横跨左右儿子的区间。

(1)和(2)这两种情况好弄,直接继承取最值,可情况(3)呢?

除了维护区间和,区间最大连续子段和,我们还需维护紧靠左端的最大连续子段和,以及紧靠右端的最大连续子段和。

于是每次更新时,我们就可以用子区间的信息来更新当前区间了。

AC代码

#include<bits/stdc++.h>
using namespace std;
const int SIZE=5e5+10;
int n,q,a[SIZE];
struct tree{
	int l,r,dat,sum,lmax,rmax;
}e[SIZE*4];
void build(int p,int l,int r){
	e[p].l=l;e[p].r=r;
	if(l==r){
		e[p].sum=e[p].dat=e[p].lmax=e[p].rmax=a[l];
		return;
	}
	int mid=(l+r)>>1;
	build(p*2,l,mid);
	build(p*2+1,mid+1,r);
	e[p].lmax = -0x3f3f3f3f;
	e[p].rmax = -0x3f3f3f3f;
	e[p].dat = -0x3f3f3f3f;
	e[p].sum=e[p*2].sum+e[p*2+1].sum;
	e[p].lmax=max(e[p*2].lmax,e[p*2].sum+e[p*2+1].lmax);
	e[p].rmax=max(e[p*2].rmax+e[p*2+1].sum,e[p*2+1].rmax);
	e[p].dat=max(e[p*2].dat,max(e[p*2+1].dat,e[p*2].rmax+e[p*2+1].lmax));
}
void change(int p,int x,int v){
	if(e[p].l==e[p].r){
		e[p].sum=e[p].dat=e[p].lmax=e[p].rmax=v;
		return;
	}
	int mid=(e[p].l+e[p].r)>>1;
	if(x<=mid) change(p*2,x,v);
	else change(p*2+1,x,v);
	e[p].sum=e[p*2].sum+e[p*2+1].sum;
	e[p].lmax=max(e[p*2].lmax,e[p*2].sum+e[p*2+1].lmax);
	e[p].rmax=max(e[p*2].rmax+e[p*2+1].sum,e[p*2+1].rmax);
	e[p].dat=max(e[p*2].dat,max(e[p*2+1].dat,e[p*2].rmax+e[p*2+1].lmax));
}
tree ask(int p,int x,int y){
	if (x<=e[p].l && y>=e[p].r) return e[p];
	tree a,b,c;
	int ans=-0x3f3f3f3f;
	a.sum=a.lmax=a.rmax=a.dat=ans;
	b.sum=b.lmax=b.rmax=b.dat=ans;
	c.sum=0;
	int	mid=(e[p].l+e[p].r)/2;
	if (x<=mid){
		a=ask(p*2,x,y);
		c.sum+=a.sum;
	}
	if (y>mid){
		b=ask(p*2+1,x,y);
		c.sum+=b.sum;
	}
	c.dat=max(max(a.dat,b.dat),a.rmax+b.lmax);
	c.lmax=max(a.lmax,b.lmax+a.sum);
	if (x>mid) c.lmax=max(c.lmax,b.lmax);
	c.rmax=max(b.rmax,a.rmax+b.sum);
	if (y<=mid) c.rmax=max(c.rmax,a.rmax);
	return c;
}
int main(){
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	build(1,1,n);
	cin>>q;
	for(int i=1;i<=n;i++){
		int op,x,y;
		cin>>op>>x>>y;
		if(op==0){
			change(1,x,y);
		}else{
			int ans=ask(1,x,y).dat;
			cout<<ans<<endl;
		}
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值