zoukankan      html  css  js  c++  java
  • CF1175G

    CF1175G - Yet Another Partiton Problem

    题目大意

    给定序列(a_i),现在将其分成(k)段,每段([l,r])的权值定义为((r-l+1)max{a_{l..r}})

    求最小化权值总和


    分析

    显然有(mathbb{Naive})(dp)

    (dp_{i,j})表示前(i)个分了(j)段的答案,直接做复杂度为(O(n^2k))

    优化它有几种常见思路:

    1.贪心简化决策

    设每个段最大值(b_i),则一个段不能向左边扩展的条件是

    两端的(b_{i-1}<b_i)(扩展会亏),或者(a_{l_i-1}>b_i)(扩展会改变(b_i)

    这样能稍微限制一下转移,然而并不好优化

    2.决策单调性

    枚举区间进行决策的问题通常具有单调性

    于是尝试通过分治决策单调性优化到(O(nklog n))

    然而已经被垃圾数据击毙 尝试证明这是假的

    3.斜率优化

    考虑确定(max a_i)之后,两侧(r-l+1)就是一个斜率优化的转移形式

    考虑如何实现这个斜率优化

    首先将(dp_{i,j})两个维护交换,按照分段数一层层进行转移

    每一层,可以考虑在(a_i)的笛卡尔树上进行转移

    每次考虑所有跨过当前节点的转移区间

    那么就要支持:

    1.查询左子树(dp_l-l cdot a_u)的最小值

    2.更新右子树(dp'_r+rcdot a_u)的最小值


    option1

    为了实现1操作,容易想到从子树中合并凸包,或者直接进行区间凸包查询

    合并凸包的问题可以暴力李超树合并维护

    但事实上区间凸包是可以维护的,方法如下

    1.维护一个静态线段树,(O(nlog n))归并预处理凸包

    2.查询(a_u)递增,具有单调性,可以在每个被查询节点上处理一个指针

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


    option2

    子树更新答案,可以通过可持久化李超树|李超树区间修改实现

    复杂度分别为(O(nlog n),O(nlog^2n)),鉴于区间修改常数不满,差别不会太大

    但实际上也可以通过朴素凸包实现:

    从根开始dfs,每次插入一个(x+y cdot i)的线段形式

    (y)是递增的,插入具有单调性,可以维护一个栈凸包

    每次二分弹掉节点,插入自己

    (不能直接弹,因为要支持回撤,会使得原先是均摊(O(n))的弹栈操作退化为(O(n^2))

    查询也可以通过二分解决(这实际上是一个经典树上斜率优化问题)



    我当然写了李超树啦

    const int N=2e4+10,U=2e4,INF=1e9+10;
    
    int n,m,A[N];
    struct Node{
    	ll k,b;
    	Node(){}
    	Node(ll k,ll b):k(k),b(b){}
    	ll operator [] (ll x) const { return k*x+b; }
    };
    struct Tree{
    	static const int M=N*20;
    	Node s[M];
    	int ls[M],rs[M],cnt;
    	int New(){
    		int u=++cnt;
    		ls[u]=rs[u]=0,s[u]=Node(INF,INF);
    		return u;
    	}
    	void Upd(int &p,int l,int r,int ql,int qr,Node x){
    		if(x.k==INF) return;
    		if(!p) p=New();
    		int mid=(l+r)>>1;
    		if(ql<=l && r<=qr) {
    			if(x[mid]<s[p][mid]) swap(s[p],x);
    			if(l==r) return;
    			if(x[l]<s[p][l]) Upd(ls[p],l,mid,ql,qr,x);
    			if(x[r]<s[p][r]) Upd(rs[p],mid+1,r,ql,qr,x);
    			return;
    		}
    		if(ql<=mid) Upd(ls[p],l,mid,ql,qr,x);
    		if(qr>mid) Upd(rs[p],mid+1,r,ql,qr,x);
    	}
    	ll Que(int p,int l,int r,int x){
    		if(!p) return INF;	
    		if(l==r) return s[p][x];
    		int mid=(l+r)>>1;
    		return min(s[p][x],x<=mid?Que(ls[p],l,mid,x):Que(rs[p],mid+1,r,x));
    	}
    	int Union(int x,int y,int l,int r){
    		if(!x||!y) return x|y;
    		Upd(x,l,r,l,r,s[y]);
    		int mid=(l+r)>>1;
    		ls[x]=Union(ls[x],ls[y],l,mid),rs[x]=Union(rs[x],rs[y],mid+1,r);
    		return x;
    	}
    } X,Y;
    
    int ls[N],rs[N],stk[N],top,mk[N];
    int dp[110][N];
    
    int rt[N],Rt;
    void Solve(int k,int u,int l,int r){
    	if(l>r) return;
    	Solve(k,ls[u],l,u-1),Solve(k,rs[u],u+1,r);
    	rt[u]=rt[ls[u]];
    	if(u>1) X.Upd(rt[u],1,U,1,U,Node(-(u-1),dp[k][u-1]));
    	int res=X.Que(rt[u],1,U,A[u]);
    	Y.Upd(Rt,1,n,u,r,Node(A[u],res));
    	rt[u]=X.Union(rt[u],rt[rs[u]],1,U);
    }
    
    int main(){
    	X.s[0]=Y.s[0]=Node(INF,INF);
    	n=rd(),m=rd();
    	rep(i,1,n) {
    		A[i]=rd();
    		while(top && A[stk[top]]<A[i]) ls[i]=stk[top--];
    		if(top) rs[stk[top]]=i;
    		stk[++top]=i;
    	}
    	rep(i,1,n) mk[ls[i]]=mk[rs[i]]=1;
    	int rt=0;
    	rep(i,1,n) if(!mk[i]) rt=i;
    	int ma=0;
    	rep(i,1,n) cmax(ma,A[i]),dp[1][i]=ma*i;
    	rep(i,1,m-1) {
    		X.cnt=0,Y.cnt=0;
    		Solve(i,rt,1,n);
    		rep(j,1,n) dp[i+1][j]=Y.Que(1,1,n,j);
    	}
    	printf("%d
    ",dp[m][n]);
    }
    
  • 相关阅读:
    109 01 Android 零基础入门 02 Java面向对象 03 综合案例(学生信息管理) 03 新增功能及实现 05 问题解析--通过一个方法完成学生和专业的双向关联
    108 01 Android 零基础入门 02 Java面向对象 03 综合案例(学生信息管理) 03 新增功能及实现 04 问题解析--数组未实例化造成的空指针异常
    107 01 Android 零基础入门 02 Java面向对象 03 综合案例(学生信息管理) 03 新增功能及实现 03 编写方法完成学生个数统计功能
    106 01 Android 零基础入门 02 Java面向对象 03 综合案例(学生信息管理) 03 新增功能及实现 02 新增属性完成学生信息存储
    105 01 Android 零基础入门 02 Java面向对象 03 综合案例(学生信息管理) 03 新增功能及实现 01 新增需求及分析
    session与cookie的区别和联系
    session和cookie的区别
    Web服务器主动推送技术
    webSocket的场景应用
    TCP、Http和Socket 优劣比较
  • 原文地址:https://www.cnblogs.com/chasedeath/p/14800706.html
Copyright © 2011-2022 走看看