zoukankan      html  css  js  c++  java
  • 「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;
    	}
    }
    
  • 相关阅读:
    【乱侃】How do they look them ?
    【softeware】Messy code,some bug of Youdao notebook in EN win7
    【随谈】designing the login page of our project
    【web】Ad in security code, making good use of resource
    SQL数据库内存设置篇
    关系数据库的查询优化策略
    利用SQL未公开的存储过程实现分页
    sql语句总结
    sql中使用cmd命令注销登录用户
    SQLServer 分页存储过程
  • 原文地址:https://www.cnblogs.com/chasedeath/p/14528225.html
Copyright © 2011-2022 走看看