把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【CF1648D】Serious Business(CDQ分治)

题目链接

  • 给定一个 \(3\times n\) 的网格图,每个格子有一个权值 \(a_{i,j}\)
  • 第一行和第三行所有格子可通行,初始第二行所有格子不可通行。
  • \(m\) 个操作可供选择,第 \(i\) 个操作花费 \(k_i\) 的代价打通第二行第 \(l_i\sim r_i\) 列的格子。
  • 操作之后你需要从左上角出发,每次只能向右或向下行走一个格子,走到右下角。
  • 求所经格子权值总和减操作代价的最大值。
  • \(1\le n,q\le5\times10^5\)\(-10^9\le a_{i,j}\le10^9\)

简单转化

假设我们在 \((1,i),(2,j)\) 两个位置选择向下走,则所经权值总和为:

\[\sum_{k=1}^ia_{1,k}+\sum_{k=i}^ja_{2,k}+\sum_{k=j}^na_{3,k} \]

\(A_{i,j}\) 为第 \(i\) 行前 \(j\) 个格子内的数之和。

那么上式可以写作:

\[A_{1,i}-A_{2,i-1}+A_{2,j}-A_{3,j-1}+A_{3,n} \]

发现这完全可以分成分别只与 \(i,j\) 有关的两部分,不妨分别记作 \(V1_i,V2_j\)

那么现在的问题就是对所有 \(i,j\),求 \(V1_i+V2_j\) 减去完全覆盖 \(i\sim j\) 的最小代价的最大值。

直接区间覆盖?

一开始有一个想法,把第一行选择 \(i\) 当成一个区间 \([0,i-1]\),第三行选择 \(j\) 当成一个区间 \([j+1,n+1]\),那么就是要选择若干个区间完全覆盖 \([0,n+1]\)

首先发现这个做法有一个问题,就是可能会在第一行选择多个 \([0,i-1]\) 或在第三行选择多个 \([j+1,n+1]\)。因此特判它们的转移,强制 \([0,i-1]\) 不能在之前答案基础上转移,\([j+1,n+1]\) 只用于更新答案而不做转移。

这样一看似乎只要将所有操作按右端点排序,便可以用一个很简单的单点修改、后缀求最大值的树状数组解决本题。

然而这里其实存在一个大问题,就是这样做不能保证 \(i\le j\),而在 \(i > j\) 的情况下可能会求出一个更大的错误答案。(我是 WA 掉之后才发现的

CDQ 分治

对这种限制容易想到 CDQ 分治去化解。

即假设当前考虑 \(i,j\) 都取在区间 \([l,r]\) 中的答案,则在这里讨论 \(i,j\) 分别在左右部分的答案,递归处理 \(i,j\) 在同一部分内的答案。特殊地,在边界讨论 \(i=j=l\) 的答案。

由此可以保证 \(i\le j\),那么就可以按照前面的思路,把这当成一个区间覆盖问题用树状数组来做了。

但似乎还有一个问题,我们处理某个区间的答案时,需要用到所有和这个区间有交的操作,复杂度假了?

实际上,对于完全包含当前区间的操作,我们只需记下代价最小的那个的代价,因为只需要这一个操作就能结合选择 \(i,j\) 完成覆盖。

这样一来处理某个区间的答案时需要的只是和它有交且不包含它的操作,发现这极类似于线段树分治的过程,复杂度正确。

(这题据说存在只用线段树的一只 \(\log\) 的做法,不过具体实现起来好像还不一定有这个做法跑得快)

代码:\(O(n\log^2n)\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Rg register
#define RI Rg int
#define Cn const
#define CI Cn int&
#define I inline
#define W while
#define N 500000
#define LL long long
#define INF (LL)1e18
using namespace std;
namespace FastIO
{
	#define FS 100000
	#define tc() (FA==FB&&(FB=(FA=FI)+fread(FI,1,FS,stdin),FA==FB)?EOF:*FA++)
	#define D isdigit(oc=tc())
	int ff;char oc,FI[FS],*FA=FI,*FB=FI;
	Tp I void read(Ty& x) {x=0,ff=1;W(!D) ff=oc^'-'?1:-1;W(x=(x<<3)+(x<<1)+(oc&15),D);x*=ff;}
	Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
}using namespace FastIO;
int n,m,a[3][N+5];LL V1[N+5],V2[N+5],A[N+5],B[N+5],C[N+5];
struct Il {int l,r,v;bool operator < (Cn Il& o) Cn {return r<o.r;}};vector<Il> g[50];
struct TreeArray//树状数组
{
	int lim;LL a[N+5];I void Init(CI l,CI r) {lim=r+1;for(RI i=l;i<=lim;++i) a[i]=-INF;}
	I void U(RI x,LL v) {++x;W(x&&a[x]<v) a[x]=v,x-=x&-x;}//单点修改
	I LL Q(RI x) {LL t=-INF;++x;W(x<=lim) t=max(t,a[x]),x+=x&-x;return t;}//后缀求最大值
}T;
int ct;LL ans=-INF;I void Solve(CI o,CI l,CI r,LL c)//分治,c记录完全包含当前区间的操作的最小代价
{
	if(l==r) return (void)(ans=max(ans,V1[l]+V2[l]-c));//边界上处理i=j
	RI i,u=l+r>>1;LL v1=-INF,v2=-INF;for(i=l;i<=u;++i) v1=max(v1,V1[i]);for(;i<=r;++i) v2=max(v2,V2[i]);ans=max(ans,v1+v2-c);//使用完全包含当前区间的操作
	LL t;vector<Il>::iterator it;for(T.Init(l,r),i=l-1,it=g[o].begin();i<=r;++i)//区间覆盖问题
		{t=-INF;W(it!=g[o].end()&&(it->r==i||i==r)) t=max(t,T.Q(max(it->l,l)-1)-it->v),++it;i<u&&(t=max(t,V1[i+1])),T.U(i,t);}//特殊转移选择i
	for(i=u+1;i<=r;++i) ans=max(ans,T.Q(i)+V2[i]);//枚举j更新答案
	RI lp=++ct,rp=++ct;LL lc=c,rc=c;g[lp].clear(),g[rp].clear();
	for(it=g[o].begin();it!=g[o].end();++it)//分配操作
	{
		if(it->l<=l&&u<=it->r) lc=min(lc,(LL)it->v);else if(max(it->l,l)<=min(it->r,u)) g[lp].push_back(*it);//完全包含更新c,否则若有交就传下去
		if(it->l<=u+1&&r<=it->r) rc=min(rc,(LL)it->v);else if(max(it->l,u+1)<=min(it->r,r)) g[rp].push_back(*it);//完全包含更新c,否则若有交就传下去
	}
	Solve(lp,l,u,lc),Solve(rp,u+1,r,rc),ct-=2;//递归
}
int main()
{
	RI i,j;for(read(n,m),j=0;j<=2;++j) for(i=1;i<=n;++i) read(a[j][i]);
	RI l,r,v;LL c=INF;for(i=1;i<=m;++i) read(l,r,v),l==1&&r==n?c=min(c,(LL)v):(g[0].push_back((Il){l,r,v}),0);//覆盖整个序列的操作直接记下
	for(i=1;i<=n;++i) A[i]=A[i-1]+a[0][i],B[i]=B[i-1]+a[1][i];for(i=n;i;--i) C[i]=C[i+1]+a[2][i];//做前缀和或后缀和
	for(i=1;i<=n;++i) V1[i]=A[i]-B[i-1],V2[i]=C[i]+B[i];//记下选择i,j的价值
	return sort(g[0].begin(),g[0].end()),Solve(0,1,n,c),printf("%lld\n",ans),0;
}
posted @ 2022-04-01 13:22  TheLostWeak  阅读(53)  评论(0编辑  收藏  举报