Codeforces 题面传送门 & 洛谷题面传送门

这是一道李超线段树的毒瘤题。

首先我们可以想到一个非常 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\)​ 前面的部分进行分段,每一段用一个三元组 \((L,R,M)\) 表示,表示 \(\forall l\in[L,R]\) 都有 \(\max\limits_{t=l+1}^ia_t=M\),那么对于 \([L,R]\) 这个区间中的任何一个 \(l\),从 \(dp_{l,j-1}\) 转移到 \(dp_{i,j}\) 的贡献都是 \(dp_{l,j-1}+(i-l)·M\),也就是说上式可以写成 \(dp_{i,j}=\min\limits_{(L,R,M)}\{\min\limits_{l=L}^Rdp_{l,j-1}+(i-l)·M\}\),注意到对于一个固定的 \(M\),我们只需要求出 \(dp_{l,j-1}-lM\) 的最小值 \(mn\),这一段转移到 \(dp_{i,j}\) 的贡献就是 \(iM-mn\)。如果我们令 \(k=M,x=i,b=-mn\),那么上式就可以写成 \(kx+b\)。想到了什么?没错,斜率优化,李超线段树,我们考虑以原序列中的下标为下标建一棵李超线段树,那么每一段的贡献就是一条直线,将它们插入李超线段树,那么查询 \(dp_{i,j}\) 时就查询第 \(i\) 位置的最小值即可。

不过注意到我们这个连续段也不是一成不变的,在单调栈维护最大值的段时还会出现弹栈操作,具体来说,当加入一个 \(a_i\) 时候我们会不断弹出栈顶元素直到栈为空或栈顶元素 \(>a_i\),这样带来的副作用就是以这些弹出的这些元素为右端点的连续段全部都会消失,取而代之的是一个大连续段,满足这个连续段中的最大值为 \(a_i\),那么我们就需要在李超线段树中删除这些直线,注意到我们是按时间顺序插入这些直线的,因此删除时肯定也会删除新插入的几条直线,因此我们像线段树分治那样开一个栈维护操作序列然后不断弹出栈顶元素并将李超线段树上对应元素改回其以前的版本知道回到操作前的序列为止。还有一个问题,就是这一段的 \(dp_{l,j-1}-lM\) 也会改变,这个看似很好维护,实则比较困难。注意到我们是要对于新的 \(M\),求出 \(dp_{l,j-1}-lM\) 的最小值,如果我们再设 \(k=-l,x=M,b=dp_{l,j-1}\),那么柿子可以写作 \(kx+b\)。想到了什么?没错你没听错,还是李超线段树,我们考虑对每个连续段再建一棵李超线段树,维护这个区间中形如 \(-lx+dp_{l,j-1}\),那么弹栈过程中这些李超线段树就会合并,因此我们像 CF932F 那样合并这些连续段对应的李超线段树即可查询 \(dp_{l,j-1}-lM\) 的最小值。

时间复杂度大概是 \(\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;
}

Codeforces 1175G - Yet Another Partiton Problem(李超线段树)的更多相关文章

  1. Codeforces 1175G Yet Another Partiton Problem [DP,李超线段树]

    Codeforces 思路 首先吐槽一句:partiton是个什么东西?我好像在百度翻译里面搜不到呀qwq 发现不了什么性质,那就直接上DP吧.注意到DP可以分层,所以设\(dp_i\)表示当前层,分 ...

  2. Codeforces 1303G - Sum of Prefix Sums(李超线段树+点分治)

    Codeforces 题面传送门 & 洛谷题面传送门 个人感觉这题称不上毒瘤. 首先看到选一条路径之类的字眼可以轻松想到点分治,也就是我们每次取原树的重心 \(r\) 并将路径分为经过重心和不 ...

  3. Codeforces Round #463 F. Escape Through Leaf (李超线段树合并)

    听说正解是啥 set启发式合并+维护凸包+二分 根本不会啊 , 只会 李超线段树合并 啦 ... 题意 给你一颗有 \(n\) 个点的树 , 每个节点有两个权值 \(a_i, b_i\) . 从 \( ...

  4. 【BZOJ-4515】游戏 李超线段树 + 树链剖分 + 半平面交

    4515: [Sdoi2016]游戏 Time Limit: 40 Sec  Memory Limit: 256 MBSubmit: 304  Solved: 129[Submit][Status][ ...

  5. 【BZOJ-3165】Segment 李超线段树(标记永久化)

    3165: [Heoi2013]Segment Time Limit: 40 Sec  Memory Limit: 256 MBSubmit: 368  Solved: 148[Submit][Sta ...

  6. 【BZOJ-1568】Blue Mary开公司 李超线段树 (标记永久化)

    1568: [JSOI2008]Blue Mary开公司 Time Limit: 15 Sec  Memory Limit: 162 MBSubmit: 557  Solved: 192[Submit ...

  7. BZOJ_2298_[HAOI2011]problem a_线段树

    BZOJ_2298_[HAOI2011]problem a_线段树 Description 一次考试共有n个人参加,第i个人说:“有ai个人分数比我高,bi个人分数比我低.”问最少有几个人没有说真话( ...

  8. 【BZOJ3165】[HEOI2013]Segment(李超线段树)

    [BZOJ3165][HEOI2013]Segment(李超线段树) 题面 BZOJ 洛谷 题解 似乎还是模板题QwQ #include<iostream> #include<cst ...

  9. 【BZOJ1568】[JSOI2008]Blue Mary开公司(李超线段树)

    [BZOJ1568][JSOI2008]Blue Mary开公司(李超线段树) 题面 BZOJ 洛谷 题解 是模板题啊. #include<iostream> #include<cs ...

随机推荐

  1. 学了ES6,还不会Promise的链式调用?🧐

    前言 本文主要讲解promise的链式调用的方法及其最终方案 应用场景 假如开发有个需求是先要请求到第一个数据,然后根据第一个数据再去请求第二个数据,再根据第二个数据去请求第三个数据...一直到最后得 ...

  2. Java:AQS 小记-2(ReentrantLock)

    Java:AQS 小记-2(ReentrantLock) 整体结构 ReentrantLock 类图 AbstractOwnableSynchronizer 类 public abstract cla ...

  3. 【二食堂】Beta - Scrum Meeting 10

    Scrum Meeting 10 例会时间:5.25 18:30~18:50 进度情况 组员 当前进度 今日任务 李健 1. 继续文本导入.保存部分的工作issue 2. 完成了技术博客 1. 继续文 ...

  4. Noip模拟41 2021.8.16

    T1 你相信引力吗 对于区间的大小关系问题,往往使用单调栈来解决 这道题的优弧和劣弧很烦,考虑将其等价的转化 由于所有的合法情况绕过的弧都不会经过最高的冰锥, 又因为环可以任意亲定起点,这样可以直接把 ...

  5. STM32 禁用或开启总中断

    今天把之前自己的一些在中断方面所产生的疑惑把具体的解决办法给大家分享一下,希望能够帮到大家. STM32在使用时有时需要禁用全局中断,比如MCU在升级过程中需禁用外部中断,防止升级过程中外部中断触发导 ...

  6. oracle 定时任务增、删、改、查

    增: 创建一个计划任务 begin sys.dbms_job.submit(job=>:job, what=>'要定时执行的存储过程名:',--例如:包名.存储过程名;  记得写分号 ne ...

  7. Red Hat Enterprise Linux (RHEL) 9 更新了什么,即 Rocky Linux 9 和 AlmaLinux 9 展望

    请访问原文链接:https://sysin.org/blog/rhel-9-vision/,查看最新版.原创作品,转载请保留出处. 作者:gc(at)sysin.org,主页:www.sysin.or ...

  8. Web实时通信,SignalR真香,不用愁了

    前言 对于B/S模式的项目,基础的场景都是客户端发起请求,服务端返回响应结果就结束了一次连接:但在很多实际应用场景中,这种简单的请求和响应模式就显得很吃力,比如消息通知.监控看板信息自动刷新等实时通信 ...

  9. 前端调试工具DevTools处理网络请求

    DevTools处理网络请求 位置:network 1.是否启用网络处理功能 2.清除历史 3.过滤器,自定义筛选 4.是否保留先前的历史,因为每次刷新会删除历史重新加载,选中后新老请求都在可做对比 ...

  10. 博主日常工作中使用的shell脚本分享

    前言: 今天给大家分享一篇在我工作中常用的一个shell脚本,里面有一些我们常用到的shell操作.该脚本用于本地电脑和服务器交互上,实现以下功能: 自动拉取自己个人电脑上的源码到服务器上yocto包 ...