• CF1175G Yet Another Partiton Problem


    一、题目

    点此看题

    二、解法

    高科技题,但是搞爆我的却是一个 &,函数传参的时候一定要注意啊!

    (dp[w][i]) 表示前 (i) 个数划分了 (w) 段的最小权值和,转移:

    [dp[w][i]leftarrow dp[w-1][j]+(i-j) imes max[j+1...i] ]

    如果没有 (max),这大概就是一个普通的斜率优化,我们先动点手脚:

    [f[i]leftarrow g[j]-j imesmax[j+1...i]+i imes max[j+1...i] ]

    现在这个题就需要分两步解决了,处理 ( t max) 的套路是使用单调栈。现在我们考虑在一个点的管辖范围内,如何维护最优的 (g[j]-jcdot max),在单调栈变化的时候 (max) 也会变,所以这里不能简单地取最值。注意到这个东西的最小值相当于一个 ((j,g[j])) 的下凸包上,我们拿一根 (k=max) 的直线去截它,得到的最小截距就是答案。

    所以我们维护斜率单增的凸包,考虑单调栈弹栈的时候会导致凸包的合并,因为凸包的 (x) 坐标是有序的(有点像 ( t treap) 的合并),所以可以一个个插入,用双端队列搞启发式合并即可,时间复杂度 (O(nlog n)),问最值直接在凸包上二分即可。

    设问出来的最值是 (b),那么我们插入一条 (maxcdot x+b) 的直线方便 (f[i]) 的转移。插入直线可以用李超树维护,但是弹栈的时候会删除若干条以前的直接,所以我们用可持久化李超树,在栈的上一个元素的基础上做修改即可,李超树的时间复杂度 (O(nlog n))

    总时间复杂度 (O(nlog n)),注意每次做之前要清空李超树。

    三、总结

    优化转移要善于观察代价的形式,如果代价和 (dp) 下标优化那么考虑斜率优化,斜率优化统一用李超树来写。

    如果代价要分段,要想一想怎么维护分段的代价,观察形式还是最重要的,比如本题我们就选择了凸包。

    #include <cstdio>
    #include <assert.h>
    #include <iostream>
    #include <deque>
    using namespace std;
    const int M = 20005;
    const int N = 32*M;
    int read()
    {
    	int x=0,f=1;char c;
    	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
    	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
    	return x*f;
    }
    int n,m,k,cnt,zxy,rt[M],a[M],f[M],g[M],st[M],ls[N],rs[N];
    struct conv
    {
    	deque<int> q;
    	void merge(conv &r)
    	{
    		int l=r.q.size()-1;
    		if(q.size()>r.q.size())
    		{
    			for(int i=l;i>=0;i--)
    			{
    				int x=r.q[i];
    				while(q.size()>1 && 1ll*(g[x]-g[q[0]])*(q[0]-q[1])
    				>=1ll*(g[q[0]]-g[q[1]])*(x-q[0])) q.pop_front();
    				q.push_front(x);
    			}
    		}
    		else
    		{
    			swap(q,r.q);
    			for(int x:r.q)
    			{
    				while(l && 1ll*(g[x]-g[q[l]])*(q[l]-q[l-1])
    				<=1ll*(g[q[l]]-g[q[l-1]])*(x-q[l])) l--,q.pop_back();
    				l++;q.push_back(x);
    			}
    		}
    	}
    	int ask(int k)
    	{
    		int l=1,r=q.size()-1,ans=0;
    		while(l<=r)
    		{
    			int m=(l+r)>>1;
    			if(g[q[m-1]]-k*q[m-1]>=g[q[m]]-k*q[m])
    				ans=m,l=m+1;
    			else r=m-1;
    		}
    		return g[q[ans]]-k*q[ans];
    	}
    }h[M];
    struct line
    {
    	int k,b;
    	line(int K=0,int B=0) : k(K) , b(B) {}
    	int ask(int x) {return k*x+b;}
    }tr[N];
    void ins(int &x,int y,int l,int r,line t)
    {
    	x=++cnt;tr[x]=tr[y];ls[x]=ls[y];rs[x]=rs[y];
    	int mid=(l+r)>>1;
    	if(tr[x].ask(mid)>t.ask(mid)) swap(tr[x],t);
    	if(l==r) return ;
    	if(tr[x].ask(l)>t.ask(l)) ins(ls[x],ls[y],l,mid,t);
    	if(tr[x].ask(r)>t.ask(r)) ins(rs[x],rs[y],mid+1,r,t);
    }
    int ask(int x,int l,int r,int d)
    {
    	int t=tr[x].ask(d);
    	if(l==r || !x) return t;
    	int mid=(l+r)>>1;
    	if(mid>=d) return min(t,ask(ls[x],l,mid,d));
    	return min(t,ask(rs[x],mid+1,r,d));
    }
    signed main()
    {
    	n=read();k=read();tr[0].b=a[0]=2e9;
    	for(int i=1,mx=0;i<=n;i++)
    	{
    		a[i]=read();
    		mx=max(mx,a[i]);
    		f[i]=mx*i;
    	}
    	for(int w=2;w<=k;w++)
    	{
    		swap(f,g);
    		for(int i=1;i<=n;i++) rt[i]=0;
    		m=cnt=0;//attention
    		for(int i=w;i<=n;i++)
    		{
    			h[i].q.resize(1);
    			h[i].q[0]=i-1;
    			while(m && a[st[m]]<=a[i]) h[i].merge(h[st[m--]]);
    			line t=line(a[i],h[i].ask(a[i]));
    			ins(rt[i],rt[st[m]],1,n,t);
    			f[i]=ask(rt[i],1,n,i);
    			st[++m]=i;
    		}
    	}
    	printf("%d
    ",f[n]);
    }
    
  • 相关阅读:
    禁用aspx页面的客户端缓存
    水晶报表的自动换行(转)
    ORACLE锁的管理
    同时使用有线和无线
    Oracle系统表的查询
    Oracle中临时表的深入研究
    我的My Life Rate
    [学习笔记]c#Primer中文版命名空间
    出差兰州·火车上
    [学习笔记]c#Primer中文版类设计、static成员、const和readonly数据成员
  • 原文地址:https://www.cnblogs.com/C202044zxy/p/15049733.html
Copyright © 2020-2023  润新知