[JSOI2011] 柠檬(斜率优化DP,优化技巧)

2024-03-01 09:20

本文主要是介绍[JSOI2011] 柠檬(斜率优化DP,优化技巧),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

题面描述

题面

简要题意:
       给出一个长度为 n n n 的整数序列 s i s_i si。可以将序列任意划分成若干非空连续段。对于每一段,可以选择一个整数 s 0 s_0 s0,若该段 s 0 s_0 s0 的数量为 t t t,则该段的价值为 s 0 × t 2 s_0 \times t^2 s0×t2。请求出每一段价值之和的最大值。

       1 ≤ n ≤ 1 0 5 , 1 ≤ s i ≤ 1 0 4 1 \leq n \leq 10^5,1 \leq s_i \leq 10^4 1n1051si104

分析

       我们首先考虑怎样的一段才可能成为答案划分的一部分。如果区间 [ l , r ] [l, r] [l,r] 为一个连续段,那么我们设选出的整数为 s 0 s_0 s0。容易想到 s l = s 0 s_l = s_0 sl=s0。因为如果 s l ≠ s 0 s_l \ne s_0 sl=s0,那么我们把 s l s_l sl 单独划分成一段, [ l + 1 , r ] [l + 1, r] [l+1,r] 划分成一段,答案会更优。因为 [ l + 1 , r ] [l + 1, r] [l+1,r] 与之前答案一样,新增出来的一段 [ l , l ] [l, l] [l,l] 会多一个 s l s_l sl 的贡献。 进一步思考,我们能够发现 s r = s 0 s_r = s_0 sr=s0。这个性质和上面的类似,不再证明。
       因此我们明白了,对于一个区间 [ l , r ] [l, r] [l,r],它能够成为答案的一个连续段, 当且仅当 s l = s r s_l = s_r sl=sr,并且此时连续段选出的整数 s 0 = s l s_0 = s_l s0=sl

       基于这个性质,我们就可以dp了。设 s u m i sum_i sumi 表示 [ 1 , i ] [1, i] [1,i] s i s_i si 的数量。即 s u m i = ∑ j = 1 i [ s j = s i ] sum_i = \sum_{j = 1}^{i}[s_j = s_i] sumi=j=1i[sj=si]。设 d p i dp_i dpi 表示 i i i 个数划分成若干连续段的最大价值和。那么有转移:

       d p i ← d p i − 1 + s i dp_{i} \gets dp_{i - 1} + s_i dpidpi1+si
       d p i = max ⁡ { d p j − 1 + ( s u m i − s u m j + 1 ) 2 × s i } ( s j = s i ) dp_{i} = \max\left \{ dp_{j - 1} + (sum_{i} - sum_{j} + 1)^2 \times s_i \right \}(s_j = s_i) dpi=max{dpj1+(sumisumj+1)2×si}(sj=si)

       第一个转移式是简单的,但是第二个转移复杂度达到了 O ( n 2 ) O(n^2) O(n2)。我们考虑优化。

       观察可以发现第二个式子有点像 斜率优化,我们把式子展开进行观察。

       d p i = d p j − 1 + ( s u m i + 1 ) 2 × s i − 2 s i ( s u m i + 1 ) × s u m j + s u m j 2 × s i dp_{i} = dp_{j - 1} + (sum_i + 1)^2 \times s_i - 2s_i(sum_i + 1) \times sum_j + sum_j^2 \times s_i dpi=dpj1+(sumi+1)2×si2si(sumi+1)×sumj+sumj2×si

       由于 s i = s j s_i = s_j si=sj,我们 把第四项中的 s i s_i si 换成 s j s_j sj

       d p i = d p j − 1 + ( s u m i + 1 ) 2 × s i − 2 s i ( s u m i + 1 ) × s u m j + s u m j 2 × s j dp_i = dp_{j - 1} + (sum_i + 1)^2 \times s_i - 2s_i(sum_i + 1) \times sum_j + sum_j^2 \times s_j dpi=dpj1+(sumi+1)2×si2si(sumi+1)×sumj+sumj2×sj

       移项,可得:
       d p j − 1 + s u m j 2 × s j = 2 s i ( s u m i + 1 ) × s u m j + d p i − ( s u m i + 1 ) 2 × s i dp_{j - 1} + sum_{j}^2 \times s_j = 2s_i(sum_i + 1) \times sum_{j} + dp_{i} - (sum_i + 1)^2 \times s_i dpj1+sumj2×sj=2si(sumi+1)×sumj+dpi(sumi+1)2×si

       不难发现这就是 斜率优化 的模型,我们令 y = d p j − 1 + s u m j 2 × s j , x = s u m j y = dp_{j - 1} + sum_{j}^2 \times s_j, x = sum_j y=dpj1+sumj2×sjx=sumj。对于 i i i,斜率 k k k 是固定的,为 2 s i ( s u m i + 1 ) 2s_i(sum_i + 1) 2si(sumi+1) b = d p i − ( s u m i + 1 ) 2 × s i b = dp_i - (sum_i + 1)^2 \times s_i b=dpi(sumi+1)2×si

       由于 ( s u m i + 1 ) 2 × s i (sum_i + 1)^2 \times s_i (sumi+1)2×si 是一个定值,我们只需要让 b b b 最大即可。那么我们对不同的 s s s 值分别维护上凸包,每次二分查找 第一个 斜率 小于 当前 k k k 的点,然后 O ( 1 ) O(1) O(1) 转移即可。时间复杂度 O ( n l o g 2 n ) O(nlog_2n) O(nlog2n)

       等等!!!难道这样就结束了吗?

       我们可以发现对于一种 s s s 值,从前往后斜率是 单调递增 的。但我们要维护的是 上凸包,好像不能弹出每次 弹出队头 。 但是可以发现,队列中斜率是单调递减的,如果当前队尾不能转移给 i i i,那么它以后也不可能成为转移项,因此直接弹出队列就行了。也就是说,对于每一个 i i i,我们从 s i s_i si 的队列中从队尾往前扫,如果队尾上一个元素与队尾的斜率小于等于 k k k,那么直接弹出队尾。否则拿出队尾进行转移就好了。时间复杂度 O ( n ) O(n) O(n)

CODE:

#include<bits/stdc++.h> // 性质:区间 [l, r] 会成为划分的一部分当且仅当 sl = sr
using namespace std;
typedef long long LL;
const int N = 1e5 + 10;
int n, pos[N];
LL dp[N], sum[N], a[N];
inline LL Y(int x) {return dp[x - 1] + sum[x] * sum[x] * a[x];}
inline LL X(int x) {return sum[x];}
vector< int > q[N];
int main() {scanf("%d", &n);for(int i = 1; i <= n; i ++ ) {scanf("%lld", &a[i]);sum[i] = sum[pos[a[i]]] + 1;pos[a[i]] = i;}for(int i = 1; i <= n; i ++ ) {dp[i] = dp[i - 1] + a[i]; // 基础转移int p = q[a[i]].size() - 1;while(p > 0 && (Y(q[a[i]][p]) - Y(q[a[i]][p - 1])) <= (X(q[a[i]][p]) - X(q[a[i]][p - 1])) * (2LL * a[i] * (sum[i] + 1LL))) p --, q[a[i]].pop_back(); // 斜率单增int j = (p == -1 ? -1 : q[a[i]][p]);if(j != -1) dp[i] = max(dp[i], dp[j - 1] + (sum[i] - sum[j] + 1LL) * (sum[i] - sum[j] + 1LL) * a[i]);while(p > 0 && (Y(i) - Y(q[a[i]][p])) * (X(q[a[i]][p]) - X(q[a[i]][p - 1])) >= (Y(q[a[i]][p]) - Y(q[a[i]][p - 1])) * (X(i) - X(q[a[i]][p]))) p --, q[a[i]].pop_back();q[a[i]].push_back(i);}printf("%lld\n", dp[n]);return 0;
}

这篇关于[JSOI2011] 柠檬(斜率优化DP,优化技巧)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/761963

相关文章

从原理到实战解析Java Stream 的并行流性能优化

《从原理到实战解析JavaStream的并行流性能优化》本文给大家介绍JavaStream的并行流性能优化:从原理到实战的全攻略,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的... 目录一、并行流的核心原理与适用场景二、性能优化的核心策略1. 合理设置并行度:打破默认阈值2. 避免装箱

Python实战之SEO优化自动化工具开发指南

《Python实战之SEO优化自动化工具开发指南》在数字化营销时代,搜索引擎优化(SEO)已成为网站获取流量的重要手段,本文将带您使用Python开发一套完整的SEO自动化工具,需要的可以了解下... 目录前言项目概述技术栈选择核心模块实现1. 关键词研究模块2. 网站技术seo检测模块3. 内容优化分析模

Java实现复杂查询优化的7个技巧小结

《Java实现复杂查询优化的7个技巧小结》在Java项目中,复杂查询是开发者面临的“硬骨头”,本文将通过7个实战技巧,结合代码示例和性能对比,手把手教你如何让复杂查询变得优雅,大家可以根据需求进行选择... 目录一、复杂查询的痛点:为何你的代码“又臭又长”1.1冗余变量与中间状态1.2重复查询与性能陷阱1.

Python内存优化的实战技巧分享

《Python内存优化的实战技巧分享》Python作为一门解释型语言,虽然在开发效率上有着显著优势,但在执行效率方面往往被诟病,然而,通过合理的内存优化策略,我们可以让Python程序的运行速度提升3... 目录前言python内存管理机制引用计数机制垃圾回收机制内存泄漏的常见原因1. 循环引用2. 全局变

MySQL中查询和展示LONGBLOB类型数据的技巧总结

《MySQL中查询和展示LONGBLOB类型数据的技巧总结》在MySQL中LONGBLOB是一种二进制大对象(BLOB)数据类型,用于存储大量的二进制数据,:本文主要介绍MySQL中查询和展示LO... 目录前言1. 查询 LONGBLOB 数据的大小2. 查询并展示 LONGBLOB 数据2.1 转换为十

Python进阶之列表推导式的10个核心技巧

《Python进阶之列表推导式的10个核心技巧》在Python编程中,列表推导式(ListComprehension)是提升代码效率的瑞士军刀,本文将通过真实场景案例,揭示列表推导式的进阶用法,希望对... 目录一、基础语法重构:理解推导式的底层逻辑二、嵌套循环:破解多维数据处理难题三、条件表达式:实现分支

Python中的filter() 函数的工作原理及应用技巧

《Python中的filter()函数的工作原理及应用技巧》Python的filter()函数用于筛选序列元素,返回迭代器,适合函数式编程,相比列表推导式,内存更优,尤其适用于大数据集,结合lamb... 目录前言一、基本概念基本语法二、使用方式1. 使用 lambda 函数2. 使用普通函数3. 使用 N

Python多线程应用中的卡死问题优化方案指南

《Python多线程应用中的卡死问题优化方案指南》在利用Python语言开发某查询软件时,遇到了点击搜索按钮后软件卡死的问题,本文将简单分析一下出现的原因以及对应的优化方案,希望对大家有所帮助... 目录问题描述优化方案1. 网络请求优化2. 多线程架构优化3. 全局异常处理4. 配置管理优化优化效果1.

MySQL中优化CPU使用的详细指南

《MySQL中优化CPU使用的详细指南》优化MySQL的CPU使用可以显著提高数据库的性能和响应时间,本文为大家整理了一些优化CPU使用的方法,大家可以根据需要进行选择... 目录一、优化查询和索引1.1 优化查询语句1.2 创建和优化索引1.3 避免全表扫描二、调整mysql配置参数2.1 调整线程数2.

Go语言网络故障诊断与调试技巧

《Go语言网络故障诊断与调试技巧》在分布式系统和微服务架构的浪潮中,网络编程成为系统性能和可靠性的核心支柱,从高并发的API服务到实时通信应用,网络的稳定性直接影响用户体验,本文面向熟悉Go基本语法和... 目录1. 引言2. Go 语言网络编程的优势与特色2.1 简洁高效的标准库2.2 强大的并发模型2.