Codeforces 1175G - Yet Another Partiton Problem(李超線段樹)
這是一道李超線段樹的毒瘤題。
首先我們可以想到一個非常 trivial 的 DP:\(dp_{i,j}\) 表示前 \(i\) 個數劃分成 \(j\) 段的最小代價,那麼顯然 \(dp_{i,j}=\min\limits_{l<i}\{dp_{l,j-1}+(i-l)·\max\limits_{t=l+1}^ia_t\}\),這樣暴力 DP 是 \(n^2k\) 的,一臉過不去。
考慮優化,注意到這裡涉及一個 \(\max\),注意到在我們 DP 掃一遍 \(a_i\) 的過程中,\(a_i\) 的最大值顯然是成段分佈的,且我們可以通過單調棧求出這些段的左端點和右端點,因此我們考慮將 \(i\)
不過注意到我們這個連續段也不是一成不變的,在單調棧維護最大值的段時還會出現彈棧操作,具體來說,當加入一個 \(a_i\) 時候我們會不斷彈出棧頂元素直到棧為空或棧頂元素 \(>a_i\),這樣帶來的副作用就是以這些彈出的這些元素為右端點的連續段全部都會消失,取而代之的是一個大連續段,滿足這個連續段中的最大值為 \(a_i\)
時間複雜度大概是 \(\mathcal O(nk\log 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\n",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\n",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}\n",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}\n",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\n",i,j,dp[i][j]);
}
} printf("%lld\n",dp[n][k]);
return 0;
}