「JOISC 2020 Day3」星座 3 (dp)

「JOISC 2020 Day3」星座 3 (dp)

考虑根据(A_i)的值建立笛卡尔树,此时平面被划分为个矩形空间

下称选择一个点为保留一个星星

Snipaste_2021-03-13_11-21-27.png

具体的,对于笛卡尔树上的节点((u,l,r)),它的矩形就是父节点矩形以下,且满足(xin[l,r],y>A_u)的部分

可以用一个线段树来查询矩形内部的点,线段树上每个节点维护(y_{max}),每次剥掉(y_{max}>A_u)的部分

复杂度为均摊(O(nlog n))

[ ]

观察笛卡尔树的树形,容易发现,

1.笛卡尔树左右子树的矩形之间不会产生贡献

2.每个节点对应的矩形区间内最多选择一个点

3.如果一个节点((u,l,r))的祖先中有一个(x_iin[l,r])的点选择了,那么自己的矩形内不能选择点

那么令(dp_{u,i})表示父节点传下来的点(x=i)时,(u)子树内的答案

对于(iin[l,r])的情况,可以直接将儿子的值合并,加上自己区间内部的权值总和(C_i)

对于(i ot in[l,r])的情况,这一部分答案相同

可以从自己子区间内选择一个点((x_i,y_i,c_i))下传,此时沿用上面合并得到的(dp)

(outans=min{dp_{x_i}+sum-c_i})

如何实现这个奇怪的(dp)过程?

考虑子树的区间不交,因此对于((u,l,r)),只维护(l,r)内部的答案,对于(i ot in[l,r])的部分额外记录一个值(dp_u)

考虑用一棵静态的线段树维护(dp),线段树上存储(iin[l,r])的答案

合并左右儿子时,两个儿子的区间不交

因此,实际上答案就是将(dp_{ls})加到([u,r])上,将(dp_{rs})加到([l,u])

处理出(sum)之后,区间修改([l,r])的答案,对于(dp_u)直接按照上面的方法枚举((x_i,y_i,c_i))来计算即可

复杂度为(O(nlog n))

int n,A[N];
struct SegFinder{
	vector <Pii> V[N];
	int s[N<<2];
	void Build(int p,int l,int r){
		if(l==r) {
			sort(V[l].begin(),V[l].end());
			s[p]=V[l].empty()?0:V[l].back().first;
			return;
		}
		int mid=(l+r)>>1;
		Build(p<<1,l,mid),Build(p<<1|1,mid+1,r);
		s[p]=max(s[p<<1],s[p<<1|1]);
	}
	void Get(int p,int l,int r,int ql,int qr,int x,vector <Pii> &Res){
		if(s[p]<x) return;
		if(l==r) {
			while(!V[l].empty() && V[l].back().first>=x) Res.pb(mp(l,V[l].back().second)),V[l].pop_back();
			s[p]=V[l].empty()?0:V[l].back().first;
			return;
		}
		int mid=(l+r)>>1;
		if(ql<=mid) Get(p<<1,l,mid,ql,qr,x,Res);
		if(qr>mid) Get(p<<1|1,mid+1,r,ql,qr,x,Res);
		s[p]=max(s[p<<1],s[p<<1|1]);
	}
} Finder;

int ls[N],rs[N],stk[N],top,mk[N];
int rt[N];
ll dp[N],s[N<<2],t[N<<2];
ll Que(int p,int l,int r,int ql,int qr){
	if(ql<=l && r<=qr) return s[p];
	int mid=(l+r)>>1; ll res=1e18;
	if(ql<=mid) cmin(res,Que(p<<1,l,mid,ql,qr));
	if(qr>mid) cmin(res,Que(p<<1|1,mid+1,r,ql,qr));
	return res+t[p];
}

void Upd(int p,int l,int r,int ql,int qr,ll x){
	if(ql<=l && r<=qr) {
		s[p]+=x,t[p]+=x;
		return;
	}
	int mid=(l+r)>>1;
	if(ql<=mid) Upd(p<<1,l,mid,ql,qr,x);
	if(qr>mid) Upd(p<<1|1,mid+1,r,ql,qr,x);
	s[p]=min(s[p<<1],s[p<<1|1])+t[p];
}

// 线段树内存储的是 如果父节点有下传下来的答案
// dp 存储没有父节点下传的答案

void Solve(int p,int l,int r){
	vector <Pii> V; Finder.Get(1,1,n,l,r,A[p]+1,V);
	// 拿出我的决策矩形

	if(l<p) Solve(ls[p],l,p-1);
	if(p<r) Solve(rs[p],p+1,r);
	if(rs[p]) Upd(1,1,n,l,p,dp[rs[p]]);
	if(ls[p]) Upd(1,1,n,p,r,dp[ls[p]]);
	ll sum=0;
	for(Pii i:V) sum+=i.second;
	if(sum) Upd(1,1,n,l,r,sum); 
	// 如果父节点有下传,那么自己必须被清空
	// 否则考虑选择一个下传下去,这样就能得到 没有父节点下传时的值
	dp[p]=Que(1,1,n,l,r);
	for(Pii i:V) cmin(dp[p],Que(1,1,n,i.first,i.first)-i.second);
}

int main(){
	n=rd();
	rep(i,1,n) {
		A[i]=rd();
		while(top && A[stk[top]]<=A[i]) ls[i]=stk[top--];
		stk[++top]=i;
	}
	top=0;
	drep(i,n,1) {
		while(top && A[stk[top]]<A[i]) rs[i]=stk[top--];
		stk[++top]=i;
	}
	rep(i,1,n) mk[ls[i]]=mk[rs[i]]=1;
	rep(_,1,rd()) {
		int x=rd(),y=rd(),c=rd();
		Finder.V[x].pb(mp(y,c));
	}
	Finder.Build(1,1,n);
	rep(i,1,n) if(!mk[i]) {
		Solve(i,1,n);
		printf("%lld
",dp[i]);
		return 0;
	}
}
原文地址:https://www.cnblogs.com/chasedeath/p/14528225.html