• Codeforces 1175G


    Codeforces 题面传送门 & 洛谷题面传送门

    这是一道李超线段树的毒瘤题。

    首先我们可以想到一个非常 trivial 的 DP:(dp_{i,j})​ 表示前 (i)​ 个数划分成 (j)​ 段的最小代价,那么显然 (dp_{i,j}=minlimits_{l<i}{dp_{l,j-1}+(i-l)·maxlimits_{t=l+1}^ia_t}),这样暴力 DP 是 (n^2k) 的,一脸过不去。

    考虑优化,注意到这里涉及一个 (max),注意到在我们 DP 扫一遍 (a_i) 的过程中,(a_i) 的最大值显然是成段分布的,且我们可以通过单调栈求出这些段的左端点和右端点,因此我们考虑将 (i)​ 前面的部分进行分段,每一段用一个三元组 ((L,R,M)) 表示,表示 (forall lin[L,R]) 都有 (maxlimits_{t=l+1}^ia_t=M),那么对于 ([L,R]) 这个区间中的任何一个 (l),从 (dp_{l,j-1}) 转移到 (dp_{i,j}) 的贡献都是 (dp_{l,j-1}+(i-l)·M),也就是说上式可以写成 (dp_{i,j}=minlimits_{(L,R,M)}{minlimits_{l=L}^Rdp_{l,j-1}+(i-l)·M}),注意到对于一个固定的 (M),我们只需要求出 (dp_{l,j-1}-lM) 的最小值 (mn),这一段转移到 (dp_{i,j}) 的贡献就是 (iM-mn)。如果我们令 (k=M,x=i,b=-mn),那么上式就可以写成 (kx+b)。想到了什么?没错,斜率优化,李超线段树,我们考虑以原序列中的下标为下标建一棵李超线段树,那么每一段的贡献就是一条直线,将它们插入李超线段树,那么查询 (dp_{i,j}) 时就查询第 (i) 位置的最小值即可。

    不过注意到我们这个连续段也不是一成不变的,在单调栈维护最大值的段时还会出现弹栈操作,具体来说,当加入一个 (a_i) 时候我们会不断弹出栈顶元素直到栈为空或栈顶元素 (>a_i),这样带来的副作用就是以这些弹出的这些元素为右端点的连续段全部都会消失,取而代之的是一个大连续段,满足这个连续段中的最大值为 (a_i),那么我们就需要在李超线段树中删除这些直线,注意到我们是按时间顺序插入这些直线的,因此删除时肯定也会删除新插入的几条直线,因此我们像线段树分治那样开一个栈维护操作序列然后不断弹出栈顶元素并将李超线段树上对应元素改回其以前的版本知道回到操作前的序列为止。还有一个问题,就是这一段的 (dp_{l,j-1}-lM) 也会改变,这个看似很好维护,实则比较困难。注意到我们是要对于新的 (M),求出 (dp_{l,j-1}-lM) 的最小值,如果我们再设 (k=-l,x=M,b=dp_{l,j-1}),那么柿子可以写作 (kx+b)。想到了什么?没错你没听错,还是李超线段树,我们考虑对每个连续段再建一棵李超线段树,维护这个区间中形如 (-lx+dp_{l,j-1}),那么弹栈过程中这些李超线段树就会合并,因此我们像 CF932F 那样合并这些连续段对应的李超线段树即可查询 (dp_{l,j-1}-lM) 的最小值。

    时间复杂度大概是 (mathcal O(nklog n)),具体证明大概就考虑每条直线在李超线段树上的深度,显然是不降的,而线段树深度最多 (log n),因此这些直线在一轮(求解第二维相同的 DP 值)中下移的次数最多 (log n)

    const int MAXN=2e4;
    const int MAXV=2e4;
    const int MAXK=100;
    const int MAXP=MAXN*30;
    const ll INF=0x3f3f3f3f3f3f3f3fll;
    int n,k,a[MAXN+5];ll dp[MAXN+5][MAXK+5];
    struct line{
    	ll k,b;
    	line(ll _k=0,ll _b=INF):k(_k),b(_b){}
    	ll get(int x){return 1ll*k*x+b;}
    } lns[MAXN*2+5];
    int lcnt=0;
    struct node{int ch[2],mx;} s[MAXP+5];
    int rt[MAXN+5],R=0,ocnt=0,ncnt=0;
    struct chg{int k,on,ori;} op[MAXP+5];
    void deal(int k,int id,int o){
    //	printf("deal %d %d %d
    ",k,id,o);
    	if(o) op[++ocnt]={k,o,s[k].mx};
    	s[k].mx=id;
    }
    void insert(int &k,int l,int r,int v,int is){
    	if(!k) return k=++ncnt,deal(k,v,is),void();int mid=l+r>>1;
    	ll l1=lns[s[k].mx].get(l),r1=lns[s[k].mx].get(r),m1=lns[s[k].mx].get(mid);
    	ll l2=lns[v].get(l),r2=lns[v].get(r),m2=lns[v].get(mid);
    	if(l1<=l2&&r1<=r2) return;
    	if(l2<=l1&&r2<=r1) return deal(k,v,is),void();
    	if(m2<=m1){
    		if(l2<=l1) insert(s[k].ch[1],mid+1,r,s[k].mx,is),deal(k,v,is);
    		else insert(s[k].ch[0],l,mid,s[k].mx,is),deal(k,v,is);
    	} else {
    		if(l2<=l1) insert(s[k].ch[0],l,mid,v,is);
    		else insert(s[k].ch[1],mid+1,r,v,is);
    	}
    }
    int merge(int x,int y,int l,int r){
    	if(!x||!y) return x+y;insert(x,l,r,s[y].mx,0);
    	int mid=l+r>>1;//printf("%d %d
    ",s[x].mx,s[y].mx);
    	s[x].ch[0]=merge(s[x].ch[0],s[y].ch[0],l,mid);
    	s[x].ch[1]=merge(s[x].ch[1],s[y].ch[1],mid+1,r);
    	return x;
    }
    ll query(int k,int l,int r,int p){
    	if(!k) return INF;int mid=l+r>>1;
    	if(l==r) return lns[s[k].mx].get(p);
    	return min((p<=mid)?query(s[k].ch[0],l,mid,p):query(s[k].ch[1],mid+1,r,p),
    	lns[s[k].mx].get(p));
    }
    int stk[MAXN+5],tp=0;
    void clear(){
    	memset(rt,0,sizeof(rt));
    	for(int i=1;i<=ncnt;i++) s[i].ch[0]=s[i].ch[1]=s[i].mx=0;
    	ncnt=lcnt=ocnt=tp=R=0;
    }
    int main(){
    	scanf("%d%d",&n,&k);
    	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    	memset(dp,63,sizeof(dp));dp[0][0]=0;
    	for(int j=1;j<=k;j++){
    		clear();
    		for(int i=1;i<=n;i++){
    			lns[++lcnt]=line(-(i-1),dp[i-1][j-1]);
    			insert(rt[i],1,MAXV,lcnt,0);
    //			printf("lns[%d]={%lld,%lld}
    ",lcnt,lns[lcnt].k,lns[lcnt].b);
    			while(tp>0&&a[stk[tp]]<a[i]){
    				while(ocnt>0&&op[ocnt].on==stk[tp]) s[op[ocnt].k].mx=op[ocnt].ori,ocnt--;
    				rt[i]=merge(rt[i],rt[stk[tp]],1,MAXV);tp--;
    			} lns[++lcnt]=line(a[i],query(rt[i],1,MAXV,a[i]));
    //			printf("lns[%d]={%lld,%lld}
    ",lcnt,lns[lcnt].k,lns[lcnt].b);
    			insert(R,1,n,lcnt,i);dp[i][j]=query(R,1,n,i);
    			stk[++tp]=i;
    //			printf("%d %d %lld
    ",i,j,dp[i][j]);
    		}
    	} printf("%lld
    ",dp[n][k]);
    	return 0;
    }
    
  • 相关阅读:
    编译原理之理解文法和语言
    利用微信电脑最新版 反编译微信小程序 无需root
    编译程序与翻译程序、汇编程序的联系与区别,编译过程包括的几个主要阶段,解释程序与编译程序的区别
    New
    自我介绍+软工5问
    Sharepoint + Office Infopart + Quick Apps for Sharepoint搭建无纸化工作平台
    练练脑javascript写直接插入排序和冒泡排序
    TodoMVC中的Backbone+MarionetteJS+RequireJS例子源码分析之二 数据处理
    TodoMVC中的Backbone+MarionetteJS+RequireJS例子源码分析之一
    Django+Tastypie作后端,RequireJS+Backbone作前端的TodoMVC
  • 原文地址:https://www.cnblogs.com/ET2006/p/Codeforces-1175G.html
Copyright © 2020-2023  润新知