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\) 前面的部分进行分段,每一段用一个三元组 \((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(李超线段树)的更多相关文章
- Codeforces 1175G Yet Another Partiton Problem [DP,李超线段树]
Codeforces 思路 首先吐槽一句:partiton是个什么东西?我好像在百度翻译里面搜不到呀qwq 发现不了什么性质,那就直接上DP吧.注意到DP可以分层,所以设\(dp_i\)表示当前层,分 ...
- Codeforces 1303G - Sum of Prefix Sums(李超线段树+点分治)
Codeforces 题面传送门 & 洛谷题面传送门 个人感觉这题称不上毒瘤. 首先看到选一条路径之类的字眼可以轻松想到点分治,也就是我们每次取原树的重心 \(r\) 并将路径分为经过重心和不 ...
- Codeforces Round #463 F. Escape Through Leaf (李超线段树合并)
听说正解是啥 set启发式合并+维护凸包+二分 根本不会啊 , 只会 李超线段树合并 啦 ... 题意 给你一颗有 \(n\) 个点的树 , 每个节点有两个权值 \(a_i, b_i\) . 从 \( ...
- 【BZOJ-4515】游戏 李超线段树 + 树链剖分 + 半平面交
4515: [Sdoi2016]游戏 Time Limit: 40 Sec Memory Limit: 256 MBSubmit: 304 Solved: 129[Submit][Status][ ...
- 【BZOJ-3165】Segment 李超线段树(标记永久化)
3165: [Heoi2013]Segment Time Limit: 40 Sec Memory Limit: 256 MBSubmit: 368 Solved: 148[Submit][Sta ...
- 【BZOJ-1568】Blue Mary开公司 李超线段树 (标记永久化)
1568: [JSOI2008]Blue Mary开公司 Time Limit: 15 Sec Memory Limit: 162 MBSubmit: 557 Solved: 192[Submit ...
- BZOJ_2298_[HAOI2011]problem a_线段树
BZOJ_2298_[HAOI2011]problem a_线段树 Description 一次考试共有n个人参加,第i个人说:“有ai个人分数比我高,bi个人分数比我低.”问最少有几个人没有说真话( ...
- 【BZOJ3165】[HEOI2013]Segment(李超线段树)
[BZOJ3165][HEOI2013]Segment(李超线段树) 题面 BZOJ 洛谷 题解 似乎还是模板题QwQ #include<iostream> #include<cst ...
- 【BZOJ1568】[JSOI2008]Blue Mary开公司(李超线段树)
[BZOJ1568][JSOI2008]Blue Mary开公司(李超线段树) 题面 BZOJ 洛谷 题解 是模板题啊. #include<iostream> #include<cs ...
随机推荐
- javascript-jquery选择器
jquery选择器用来获得jquery对象 我们用一个实例来演示jquery与原生的区别 <div id="title">123</div>原生获得元素的方 ...
- 81. 搜索旋转排序数组 II
题目 已知存在一个按非降序排列的整数数组 nums ,数组中的值不必互不相同. 在传递给函数之前,nums 在预先未知的某个下标 k(0 <= k < nums.length)上进行了 旋 ...
- UltraSoft - Beta - Scrum Meeting 12
Date: May 28th, 2020. Scrum 情况汇报 进度情况 组员 负责 今日进度 q2l PM.后端 会议记录修复了课程中心导入作业时出现重复的问题完成了消息中心界面的交互 Liuzh ...
- STM32必学的时钟系统
STM32的时钟系统 相较于51单片机,stm32的时钟系统可以说是非常复杂了,我们现在看下面的一张图: 上图说明了时钟的走向,是从左至右的从时钟源一步步的分配给外设时钟.需要注意的是,上图左侧一 ...
- 在Vue前端界面中,几种数据表格的展示处理,以及表格编辑录入处理操作。
在Vue前端项目中,我这里主要是基于Vue+Element的开发,大多数情况下,我们利用Element的表格组件就可以满足大多数情况的要求,本篇随笔针对表格的展示和编辑处理,综合性的介绍几款表格组件的 ...
- Nginx(二):Nginx的四层(L4)和七层(L7)负载均衡
OSI七层模型 和 TCP/IP四层模型 四层负载均衡( L4 Load Balancing ) 四层负载均衡,主要通过报文中的目标地址和端口,再加上负载均衡设备设置的服务器选择方式,决定最终选择的内 ...
- 基础篇:JAVA集合,面试专用
没啥好说的,在座的各位都是靓仔 List 数组 Vector 向量 Stack 栈 Map 映射字典 Set 集合 Queue 队列 Deque 双向队列 关注公众号,一起交流,微信搜一搜: 潜行前行 ...
- 极速上手 VUE 3—v-model 的使用变化
本篇文章主要介绍 v-model 在 Vue2 和 Vue3 中使用变化. 一.Vue2 中 v-model 的使用 v-model 是语法糖,本质还是父子组件间的通信.父子组件通信时有两种方式: 父 ...
- RocketMQ源码详解 | Broker篇 · 其三:CommitLog、索引、消费队列
概述 上一章中,已经介绍了 Broker 的文件系统的各个层次与部分细节,本章将继续了解在逻辑存储层的三个文件 CommitLog.IndexFile.ConsumerQueue 的一些细节.文章最后 ...
- Linux&C———进程间通信
管道和有名管道 消息队列 共享内存 信号 套接字 由于进程之间的并不会像线程那样共享地址空间和数据空间,所以进程之间就必须有自己特有的通信方式,这篇博客主要介绍自己了解到的几种进程之间的通信方式,内容 ...