CF1175G Yet Another Partiton Problem

一、题目

点此看题

二、解法

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

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

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

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

\[f[i]\leftarrow g[j]-j\times\max[j+1...i]+i\times \max[j+1...i] \]

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

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

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

总时间复杂度 \(O(n\log 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\n",f[n]);
}
posted @ 2021-07-23 17:09  C202044zxy  阅读(83)  评论(0编辑  收藏  举报