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. Shadertoy 教程 Part 1 - 介绍

    Note: This series blog was translated from Nathan Vaughn's Shaders Language Tutorial and has been au ...

  2. 【UE4 C++】绘制函数 Debug drawing functions

    基于UKismetSystemLibrary /** Draw a debug line */ UFUNCTION(BlueprintCallable, Category="Renderin ...

  3. 【UE4 设计模式】观察者模式 Observer Pattern

    概述 描述 定义对象间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新.观察者模式又叫做 发布-订阅(Publish/Subscribe)模式 模型-视图(M ...

  4. 92.反转链表II

    题目 给你单链表的头指针 head 和两个整数 left 和 right ,其中 left <= right .请你反转从位置 left 到位置 right 的链表节点,返回 反转后的链表 . ...

  5. BUAA2020软工作业(四)——结对项目

    项目 内容 这个作业属于哪个课程 2020春季计算机学院软件工程(罗杰 任健) 这个作业的要求在哪里 结对项目作业 我在这个课程的目标是 进一步提高自己的编码能力,工程能力,团队协作能力 这个作业在哪 ...

  6. 关于stm32串口必须要学的5个串口以及串口应用和注意事项

    串口是我们常用的一个数据传输接口,STM32F103系列单片机共有5个串口. 其中1-3是通用同步/异步串行接口USART(Universal Synchronous/Asynchronous Rec ...

  7. 深入理解Linux C语言内存管理

    问题不能拖,我这就来学习一下吧,争取一次搞定. 在任何程序设计环境及语言中,内存管理都十分重要. 内存管理的基本概念 分析C语言内存的分布先从Linux下可执行的C程序入手.现在有一个简单的C源程序h ...

  8. Hash算法:双重散列

    双重散列是线性开型寻址散列(开放寻址法)中的冲突解决技术.双重散列使用在发生冲突时将第二个散列函数应用于键的想法. 此算法使用: (hash1(key) + i * hash2(key)) % TAB ...

  9. 像素设定 牛客网 程序员面试金典 C++ Python

    像素设定 牛客网 程序员面试金典 题目描述 有一个单色屏幕储存在一维数组中,其中数组的每个元素代表连续的8位的像素的值,请实现一个函数,将第x到第y个像素涂上颜色(像素标号从零开始),并尝试尽量使用最 ...

  10. 数组 & 对象 & 函数

    数组 数组也是一个对象,不同的是对象用字符串作为属性名,而数组用数字作为索引,数组的索引从0开始 创建数组: //方式一:构造器,可以在创建数组时指定 Var arr = new Array(1,2, ...