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]);
}