首先,状态设计十分显然: d p i dp_i dpi表示前 i i i个数的答案。
状态转移也十分显然: d p i = d p l − 1 + ( ∑ j = l i a j ) 2 + M dp_i=dp_{l-1}+(\sum_{j=l}^i a_j)^2+M dpi=dpl−1+(∑j=liaj)2+M。
即使使用了前缀和来优化,时间复杂度也仍只有 O ( n 2 ) O(n^2) O(n2),无法接受。
定义 d p i dp_i dpi的决策点为使得 d p i dp_i dpi的值最小的 j j j,珂以发现,当 i i i的值变大的同时, d p i dp_i dpi的决策点竟然单调不减。
我们称这个性质为“决策单调性”。
这个状态转移具有决策单调性又有什么用呢?难道可以优化到 O ( n l o g n ) O(nlogn) O(nlogn)? 是的,我们可以这么优化:
定义一个数组 p p p, p i p_i pi表示 d p i dp_i dpi的决策点。当我们想要求出 d p i dp_i dpi的时候,我们先根据 p i p_i pi的值迅速转移得到 d p i dp_i dpi;然后我们从末尾往前扫一遍这个数组 p p p,如果对于一个 j j j使得 p j p_j pj作为决策点没有 i i i作为决策点更优,那么就把这个 p j p_j pj替换掉。根据决策拥有单调性,我们可以优化这个扫描 p p p数组并尝试替换的步骤,直接大力二分,得到 x x x及其之后的决策点是 i i i更优,然后我们将 p p p数组中 [ x , n ] [x,n] [x,n]这段区间全部替换为 i i i即可。
这里涉及到“二分+单点查询,与区间摊”,可以使用线段树来维护,时间复杂度 O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)。
能不能优化到 O ( n l o g n ) O(nlogn) O(nlogn)呢?
我们学习一下珂朵莉树的思想(这是体现珂朵莉可爱的时候啦 ),我们维护许多三元组。一个三元组为 ( l , r , x ) (l,r,x) (l,r,x),表示 p l p_l pl到 p r p_r pr目前的决策点是 x x x。
每次我们:
①转移得到 d p i dp_i dpi,这个步骤没有变化。
②去掉开头无用的三元组。
即,假设我们扫描到的 i i i为 4 4 4,而最左边的那个三元组是 ( 4 , 6 , 2 ) (4,6,2) (4,6,2),可以发现 “ 4 ” “4” “4”在做完①中的转移后就没用了,那么我们就将这个三元组变成 ( 5 , 6 , 2 ) (5,6,2) (5,6,2)。还有一种情况,就是这个三元组是 ( 4 , 4 , 1 ) (4,4,1) (4,4,1),这时整个三元组都没用了,直接去掉即可。
③去掉末尾无用的三元组。我们从末尾往前扫,假设目前扫描到的三元组的开头是 l l l,而 l l l作为决策点没有 i i i作为决策点更优,那么我们就直接把这个三元组删掉。
为什么可以删呢?为什么我们只需要判断左端点就可以了呢? 因为,在看到 i i i的这一时刻,所有三元组的第三个元素的值都不会达到 i i i。即,对于一个三元组,如果对于三元组的一个 l l l, i i i作为决策点更优,那么整个三元组的决策点一定会不小于 i i i,而绝对不可能是任何小于 i i i的数。原来的决策点可以作废了,这个区间删掉就好了。
④我们可能会出现这样一种情况:
⌊ \lfloor ⌊ 一个三元组表示的一段区间中,前面的一部分的决策点不变,后面的那一部分的决策点是 i i i更优。 ⌉ \rceil ⌉
对于这样子的区间,显然有且仅有一个。我们直接在这个区间里面二分一个 m i d mid mid,使得 m i d mid mid左边的所有 d p dp dp值的决策点不变更优, m i d mid mid及其右边的 d p dp dp值的决策点变成 i i i更优。根据决策的单调性,二分的正确性有了保障。
⑤插入一段三元组 ( m i d , n , i ) (mid,n,i) (mid,n,i),即区间 [ m i d , n ] [mid,n] [mid,n]的决策点是 i i i。
放几张图:
At first:
①根据第一个三元组的决策点转移
②去掉无用的
③从末尾往前扫,假设当前三元组的左端点是 l l l,区间决策点为 p o s pos pos;而 i i i作为决策点比 p o s pos pos更佳。对于这样的区间直接删掉。
④我们在当前三元组序列末尾的区间里面二分一个 m i d mid mid,使得 m i d mid mid左边的所有 d p dp dp值的决策点不变更优, m i d mid mid及其右边的 d p dp dp值的决策点变成 i i i更优。
⑤插入一段三元组 ( m i d , n , i ) (mid,n,i) (mid,n,i),即区间 [ m i d , n ] [mid,n] [mid,n]的决策点是 i i i。
时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn)。
#include <bits/stdc++.h>
#define int long long
using namespace std;
int n,m,l=1,r=1;
int a[500005],pre[500005],dp[500005];
struct DP_triples
{
int l,r,pos;
}b[500005];
int cost(int l,int r)
{
return dp[l]+(pre[r]-pre[l])*(pre[r]-pre[l])+m;
}
int Binary(int l,int r,int i,int j)//二分那个mid
{
int p;
while (l<=r)
{
int mid=(l+r)>>1;
if (cost(i,mid)<=cost(j,mid))
{
p=mid;
r=mid-1;
}
else l=mid+1;
}
return p;
}
inline int read()
{
int s=0,w=1;
char ch=getchar();
while (ch<'0'||ch>'9')
{
if (ch=='-') w=-w;
ch=getchar();
}
while (ch>='0'&&ch<='9')
{
s=(s<<1)+(s<<3)+(ch^'0');
ch=getchar();
}
return s*w;
}
signed main()
{
while (~scanf("%lld%lld",&n,&m))
{
for (int i=1;i<=n;i++) a[i]=read();
for (int i=1;i<=n;i++) pre[i]=pre[i-1]+a[i];
l=1,r=1;
b[l].l=1,b[l].r=n,b[l].pos=0;
for (int i=1;i<=n;i++)
{
dp[i]=cost(b[l].pos,i);
if (b[l].r==i) l++;
else b[l].l++;
while (cost(b[r].pos,b[r].l)>=cost(i,b[r].l)) r--;
if (l>r)
{
r++;
b[r].l=i+1,b[r].r=n,b[r].pos=i;
}
else
{
int k;
if (cost(b[r].pos,b[r].r)<=cost(i,b[r].r)) k=b[r].r+1;
else k=Binary(b[r].l,b[r].r,i,b[r].pos);
if (k<=n)
{
b[r].r=k-1;
b[++r].l=k,b[r].r=n,b[r].pos=i;
}
}
}
cout<<dp[n]<<endl;
}
return 0;
}
回顾一下上面我们所说的几步走,里面的特判特别多。
①直接转移: 没啥特判。就算有特判,也与“决策单调性优化 d p dp dp”本身无关。
②去掉开头无用的: 一定要注意两种情况: 左端点加 1 1 1,与整个三元组都要删去。
if (b[l].r==i) l++;
else b[l].l++;
③去掉末尾错误的: 如果把整个三元组序列删成空的了,一定要补上一个 ( i + 1 , n , i ) (i+1,n,i) (i+1,n,i)并不再二分。
if (l>r)
{
r++;
b[r].l=i+1,b[r].r=n,b[r].pos=i;
}
else 二分
④二分: 特判一下整个区间的决策点都不变的情况
if (cost(b[r].pos,b[r].r)<=cost(i,b[r].r)) k=b[r].r+1;
⑤加入区间: 特判一下 m i d mid mid(即代码中的 k k k)不小于 n n n的情况。这种情况出现,当且仅当 i i i不能成为后面任何区间的更优的决策点。
if (k<=n)
{
b[r].r=k-1;
b[++r].l=k,b[r].r=n,b[r].pos=i;
}
顺便发一句牢骚,这个东西为什么叫二分栈啊……
即:
①转移;
②改头;
③删尾;
④二分;
⑤插入。
②③④⑤步各有一个特判,请注意。
这题不是 d p dp dp题,但是有决策单调性,是不是很有意思……
文章浏览阅读472次,点赞13次,收藏14次。BLOOM 作为一种先进的大型语言模型 (LLM),提供了广泛的优势,可以扩展到各种用户群。它的能力不仅直接影响某些专业人士和部门,而且具有更广泛的影响,间接影响更广泛的利益相关者。BLOOM 的开发和发布在现实世界中具有重大影响,无论是在其影响方面还是在它引发的争议方面。通过了解 BLOOM 的直接和间接用户,我们可以欣赏该模型的广泛影响力以及它为推动技术和社会进步做出贡献的多种方式。在快节奏的人工智能 (AI) 世界中,像 BLOOM 这样的大型语言模型 (LLM) 的相关性是一个持续讨论的话题。
文章浏览阅读1.1k次。文章目录1. 命令作用2. 语法3. 常用参数4. 小案列1. 命令作用逐行比较,告诉我们怎么把第一个文件改为第二个文件。显示出第一个文件和第二个文件的差异。2. 语法diff [options] file1.txt file2.txt # file1为比较文件 file2为基准比较文件.3. 常用参数 -b:不检查空格 -B:不检查空白行 -i:不检查大小写 -w:忽略所..._shell diff
文章浏览阅读1.9k次。VC++代码实现旋转立方体,没有使用DirectX、OpenGL,允许你拖动滑块改变旋转角度,也能让立方体围绕某一方向旋转,虽然功能不多,但是代码极具参考价值,像这类程序很能考验编程水平。源码下载地址:点击下载备用下载地址:点击下载..._立方体表面微结构编程代码
文章浏览阅读1.5k次。By 超神经场景描述:人类产生的废弃物有意无意地漂进大海,而这些废弃物中,许多类型的塑料无法分解,严重威胁着鱼类、海鸟、海洋爬行动物、海洋哺乳动物,以及船只和沿海居住区。..._ai技术对海洋保护的负面影响
文章浏览阅读142次。%matplotlib inline%config InlineBackend.figure_format='retina'from __future__ import absolute_import, division, print_functionimport matplotlib as mplfrom matplotlib import pyplot as pltfrom mat..._missing-ness
文章浏览阅读4.8k次。是中的一个科学计算库,提供了强大的符号计算体系,可用于多项式求值、求极限、解方程、求积分、微分方程、级数展开、矩阵运算等等计算问题。本文主要介绍使用库求解方程的方法。使用mathmath.expsympy.exp。_利用sympy的solve求解方程式
文章浏览阅读215次。zookeeper的分布式锁的实现原理是什么zk的分布式锁是基于zk的两个特性,第一是顺序节点的特性,第二是临时节点特性。顺序节点是假如多个客户端在某个目录下创建节点,zk会按照客户端发起创建请求的顺序来依次来创建节点,节点的末尾数字是递增的。序号的递增是由zk内部完成的。临时节点的意思是,当某个客户端断开链接时,其创建的节点就会被删除。根据这两个特性我们来模拟zk的分布式锁,比如在商品抢购场景中,一个客户端率先在这个商品id的目录下创建了临时顺序节点。后面的客户端再去这个商品id的目录下创建节点时跟据顺
文章浏览阅读9.4k次,点赞100次,收藏180次。解决Android导入项目时Gradle下载速度慢\超时\失败解决方法_导入gradle项目特别慢
文章浏览阅读428次。纯CSS实现表头固定之所以难,主要在两点。一是占有最大市场份额的IE6不支持position:fixed。另一个,是人们想破头都想在一起表格中实现这种效果。我们知道,CSS是负责表现,HTML是负责结构,同样的结构,换个样式,给人的感觉完全不同,这也说明人的眼睛是很容易受骗。因此前些狂热鼓吹p+CSS的日子里,人们总是想在页面去掉table,用p+span弄出了一个个“table”来。虽然这种事是..._表单顶部固定语法
文章浏览阅读1.1k次,点赞6次,收藏7次。vue 实现 todos 具体代码思路和代码给 input 框绑定回车事件,回车后把数据请求到 vuex 中把数据保存至本地存储中给下拉箭头绑定点击事件,点击则给所有多选框进行反选判断是否选中,如果选中则禁用给数组进行过滤,然后渲染至页面中双击 input 框 则允许改变样式,平时则禁止改变_todos
文章浏览阅读1.7k次,点赞3次,收藏9次。GDB是GNU Debugger的简称,其作用是可以在程序运行时,检测程序正在做些什么。GDB程序自身是使用C和C++程序编写的,但可以支持除C和C++之外很多编程语言的调试。GDB原生支持调试的语言包含:•C•C++•D•Go•Object-C•OpenCL C•Fortran•Pascal•Rust•Modula-2•Ada此外,通过扩展GDB,也可以用来调试Python语言。_gdb调试程序
文章浏览阅读108次。九度OJ--1164(C++) #include <iostream>#include <vector>using namespace std;int main() { int n; // n为矩阵阶数 while(c..._c++1164