2023-2024雅加达区域赛 F 离线+线段树维护最大子段和

有一堆区间加操作,还有一堆询问,每个询问可以从 [ s , t ] [s,t] [s,t]中选择一个非空子区间,执行序号在这个区间内的区间加操作,然后问 a k a_k ak的最大值。

首先可以先去掉一些条件,如果就是给一堆区间加操作,然后问选择一个子数组中的所有操作执行, a k a_k ak的最大值?。那显然我们应该只考虑覆盖 k k k这个位置的所有区间加操作,然后再这些操作里,他们都能对 k k k有效果,可以视为对 a k a_k ak的单点加,然后我们要选一个区间的操作,让区间元素和最大。这实际上就是一个最大子段和,可以dp。

然后如果我们加上操作序号必须在 [ s , t ] [s,t] [s,t]内这个条件,就相当于对一个区间的元素求最大子段和,这个东西可以线段数维护,和维护区间最长全0子串类似,也是维护三个 t a g tag tag:全局最大,前缀最大,后缀最大。具体来说,我们就是开一个线段树,存所有操作,如果第 i i i个操作可以区间加 x x x的话,则 t [ i ] = x t[i]=x t[i]=x,然后我们查序号在 [ s , t ] [s,t] [s,t]的所有操作的最大子段和,就是一个区间查 [ s , t ] [s,t] [s,t]

如果再加上 k k k每次询问都不同的话,那就还需要离线询问,确保每次查询 k k k的答案时,线段树中只含可以覆盖 k k k的所有区间。这可以用一个扫描线实现,每个操作都有个覆盖范围 [ l , r ] [l,r] [l,r],那我们可以从小到大遍历所有位置 i i i,在 l l l时加入这个操作,在 r r r时删除这个操作

struct Tree{
	#define ls u<<1
	#define rs u<<1|1
	struct Node{
		int l,r,mx,lmx,rmx,sum;
		
		Node operator+(const Node &o){
			Node res;
			res.mx=max({mx,o.mx,rmx+o.lmx,o.lmx,rmx});
			res.l=l;
			res.r=o.r;
			res.sum=sum+o.sum;
			
			res.lmx=max(lmx,sum+o.lmx);
			res.rmx=max(o.rmx,rmx+o.sum);
			return res;
		}
	}tr[N<<2];
	
	void pushup(int u){
		tr[u]=tr[ls]+tr[rs];
	}
	
	void build(int u,int l,int r){
		tr[u]={l,r,0,0,0,0};
		if(l==r)	return;
		int mid=(l+r)>>1;
		build(ls,l,mid);	build(rs,mid+1,r);
		pushup(u);
	}
	
	void modify(int u,int idx,int val){
		if(tr[u].l==tr[u].r){
			tr[u].sum=val;
			tr[u].lmx=tr[u].rmx=tr[u].mx=val;
			return;
		}
		else{
			int mid=(tr[u].l+tr[u].r)>>1;
			if(mid>=idx)	modify(ls,idx,val);
			else modify(rs,idx,val);
			pushup(u);	
		}
	}
	
	Node query(int u,int l,int r){
		if(l<=tr[u].l&&tr[u].r<=r)	return  tr[u];
        int mid=(tr[u].l+tr[u].r)>>1;
        if(r<=mid)return query(ls,l,r);
        if(l>mid)return query(rs,l,r);
        return query(ls,l,r)+query(rs,l, r);
	}
}t;
void solve(){
	cin>>n>>m;
	t.build(1,1,m);
	vvi add[n+1],del[n+1];
	rep(i,1,m){
		int l,r,x;
		cin>>l>>r>>x;
		add[l].push_back({x,i});
		del[r].push_back({x,i});
	}
	
	int qu;
	cin>>qu;
	vvi q[n+1];
	vi ans(qu+1);
	rep(i,1,qu){
		int s,t,k;
		cin>>k>>s>>t;
		q[k].push_back({s,t,i});
	}
	rep(i,1,n){
		for(auto &v:add[i]){
			int id=v[1],val=v[0];
			t.modify(1,id,val);
		}
		for(auto &v:q[i]){
			int l=v[0],r=v[1],id=v[2];
			ans[id]=t.query(1,l,r).mx;
		}
		for(auto &v:del[i]){
			int id=v[1],val=v[0];
			t.modify(1,id,0);
		}
	}
	rep(i,1,qu){
		cout<<ans[i]<<'\n';
	}
}	
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值