update
This commit is contained in:
parent
f663b768a0
commit
29fcb3c5de
51
src/20241120/T541792.cpp
Normal file
51
src/20241120/T541792.cpp
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
#include <bits/stdc++.h>
|
||||||
|
|
||||||
|
using ll = int64_t;
|
||||||
|
using std::cin,std::cout;
|
||||||
|
|
||||||
|
const ll maxn{ll(2e5+5)},inf{ll(1)<<60};
|
||||||
|
ll n,a[maxn],fl[maxn],fr[maxn],ans{inf},gl[maxn],gr[maxn];
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
void pa(T *t){
|
||||||
|
for(ll i{1};i<=n;i++)cout<<t[i]<<' ';
|
||||||
|
cout<<'\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(){
|
||||||
|
std::iostream::sync_with_stdio(false),std::cin.tie(nullptr),std::cout.tie(nullptr);
|
||||||
|
|
||||||
|
cin>>n;
|
||||||
|
for(ll i{1};i<=n;i++){
|
||||||
|
cin>>a[i];
|
||||||
|
}
|
||||||
|
for(ll i{2};i<=n;i++){
|
||||||
|
if(a[i-1]+fl[i-1]>=a[i]){
|
||||||
|
fl[i]=fl[i-1]+(a[i-1]-a[i])+1;
|
||||||
|
}
|
||||||
|
gl[i]=gl[i-1];
|
||||||
|
if(fl[i-1]<fl[i]){
|
||||||
|
gl[i]+=fl[i]-fl[i-1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for(ll i{n-1};i>=1;i--){
|
||||||
|
if(a[i+1]+fr[i+1]>=a[i]){
|
||||||
|
fr[i]=fr[i+1]+(a[i+1]-a[i])+1;
|
||||||
|
}
|
||||||
|
gr[i]=gr[i+1];
|
||||||
|
if(fr[i+1]<fr[i]){
|
||||||
|
gr[i]+=fr[i]-fr[i+1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// pa(fl);
|
||||||
|
// pa(fr);
|
||||||
|
// pa(gl);
|
||||||
|
// pa(gr);
|
||||||
|
for(ll i{1};i<=n;i++){
|
||||||
|
ans=std::min(
|
||||||
|
ans,
|
||||||
|
(gl[i]+gr[i])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
cout<<ans<<'\n';
|
||||||
|
}
|
261
src/20241120/T541792.md
Normal file
261
src/20241120/T541792.md
Normal file
@ -0,0 +1,261 @@
|
|||||||
|
要解决这道题,我们需要通过对数组进行子数组加1操作,使其最终形成一个“山形”序列,即存在一个峰值位置 $ k $,使得数组在 $ k $ 之前严格递增,在 $ k $ 之后严格递减。我们的目标是最小化所需的操作次数。
|
||||||
|
|
||||||
|
### 问题分析
|
||||||
|
|
||||||
|
**操作定义:**
|
||||||
|
- 每次操作选择一个连续子数组,将其中每个元素同时加1。
|
||||||
|
|
||||||
|
**目标**
|
||||||
|
- 通过最少次数的操作,使得最终数组满足存在一个峰值 $ k $,满足 $ A_1 < A_2 < \dots < A_k > A_{k+1} > \dots > A_N $。
|
||||||
|
|
||||||
|
### 思路概述
|
||||||
|
|
||||||
|
1. **明确最终数组的要求**
|
||||||
|
- **递增部分**:对于 $ i \leq k $,需要 $ A_i < A_{i+1} $。
|
||||||
|
- **递减部分**:对于 $ i > k $,需要 $ A_i > A_{i+1} $。
|
||||||
|
|
||||||
|
2. **确定需要的增量**
|
||||||
|
- 为满足递增和递减的要求,可能需要对某些元素进行增量操作。这些增量可以记录为 $ c_i $,其中 $ c_i $ 表示对位置 $ i $ 进行操作的次数(每次操作增加1)。
|
||||||
|
|
||||||
|
3. **计算每个位置所需的最小增量**
|
||||||
|
- **递增部分**:从左到右,确保每个位置的值比前一个位置大至少1。
|
||||||
|
- **递减部分**:从右到左,确保每个位置的值比后一个位置大至少1。
|
||||||
|
|
||||||
|
4. **确定最小操作次数**
|
||||||
|
- 将所有位置的增量需求转换为最少的连续子数组操作次数。实际上,这等同于统计增量需求的“层数”,即覆盖所有需要增加的部分所需的最少水平切片数量。
|
||||||
|
|
||||||
|
### 详细步骤
|
||||||
|
|
||||||
|
#### 1. 预处理递增和递减部分的增量需求
|
||||||
|
|
||||||
|
为了高效地计算每个可能的峰值位置 $ k $ 对应的增量需求,我们可以预先计算两个辅助数组:
|
||||||
|
|
||||||
|
- **递增部分的增量数组**(前缀方式):
|
||||||
|
- 定义 `pre[i]` 表示为了使前 $ i $ 个元素严格递增所需的最小增量。
|
||||||
|
- 计算方法:
|
||||||
|
```cpp
|
||||||
|
pre[1] = 0;
|
||||||
|
for (int i = 2; i <= N; ++i) {
|
||||||
|
pre[i] = max(pre[i-1] + 1 + (A[i-1] - A[i]), 0);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
这里,`pre[i]` 的意思是为了让 $ A_i + c_i > A_{i-1} + c_{i-1} $,需要至少增加多少。
|
||||||
|
|
||||||
|
- **递减部分的增量数组**(后缀方式):
|
||||||
|
- 定义 `suf[i]` 表示为了使后 $ N - i + 1 $ 个元素严格递减所需的最小增量。
|
||||||
|
- 计算方法:
|
||||||
|
```cpp
|
||||||
|
suf[N] = 0;
|
||||||
|
for (int i = N-1; i >= 1; --i) {
|
||||||
|
suf[i] = max(suf[i+1] + 1 + (A[i+1] - A[i]), 0);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
这里,`suf[i]` 的意思是为了让 $ A_i + c_i > A_{i+1} + c_{i+1} $,需要至少增加多少。
|
||||||
|
|
||||||
|
#### 2. 计算最小操作次数
|
||||||
|
|
||||||
|
为了找到最小的操作次数,我们需要为每个可能的峰值位置 $ k $ 计算所需的增量,并确定覆盖这些增量所需的最少操作次数。
|
||||||
|
|
||||||
|
**关键观察:**
|
||||||
|
- 每个操作可以覆盖一个连续的子数组,将其中所有元素加1。
|
||||||
|
- 要覆盖所需的增量 `c[i]`,可以将增量视为柱状图的高度,操作次数即为“绘制这些柱状图”所需的水平切片数。
|
||||||
|
|
||||||
|
**操作次数计算方法:**
|
||||||
|
- 对于一个特定的增量数组 `c`,最小的操作次数等于 `c[i]` 与 `c[i-1]` 的差值的正部分之和,具体表达为:
|
||||||
|
$$
|
||||||
|
\text{操作次数} = \sum_{i=1}^{N} \max(c[i] - c[i-1], 0)
|
||||||
|
$$
|
||||||
|
其中,定义 $ c[0] = 0 $ 以便处理第一个元素。
|
||||||
|
|
||||||
|
**具体步骤:**
|
||||||
|
1. **枚举所有可能的峰值位置 $ k $**
|
||||||
|
- 对于每一个 $ k $,设定数组前 $ k $ 个元素的增量为 `pre[k]`,后 $ N - k $ 个元素的增量为 `suf[k+1]`。
|
||||||
|
|
||||||
|
2. **计算每个 $ k $ 对应的总操作次数**
|
||||||
|
- 合并前缀和后缀增量需求,得到整体的增量数组 `c`:
|
||||||
|
- 对于 $ i \leq k $,`c[i] = pre[i]`
|
||||||
|
- 对于 $ i > k $,`c[i] = suf[i]`
|
||||||
|
- 计算操作次数:
|
||||||
|
```cpp
|
||||||
|
long long operations = 0;
|
||||||
|
long long prev = 0;
|
||||||
|
for (int i = 1; i <= N; ++i) {
|
||||||
|
long long current = (i <= k) ? pre[i] : suf[i];
|
||||||
|
if (current > prev)
|
||||||
|
operations += (current - prev);
|
||||||
|
prev = current;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **选择最小的操作次数**
|
||||||
|
- 遍历所有 $ k $,记录最小的 `operations` 作为答案。
|
||||||
|
|
||||||
|
#### 3. 优化计算
|
||||||
|
|
||||||
|
尽管上述方法直观,但在 $ N $ 高达 $ 2 \times 10^5 $ 时,枚举所有 $ k $ 并对每个 $ k $ 进行 $ O(N) $ 的计算会导致总时间复杂度 $ O(N^2) $,超出限制。为了优化,我们需要更高效的方法:
|
||||||
|
|
||||||
|
**优化策略:**
|
||||||
|
- 观察到对于不同的 $ k $,前缀和后缀增量需求具有重叠和单调性,可以通过预处理和动态规划的方式来减少重复计算。
|
||||||
|
- **核心思想**:最小操作次数等于前缀和后缀增量需求的联合上升序列的步数。
|
||||||
|
|
||||||
|
**具体实现:**
|
||||||
|
1. **预计算前缀操作次数和后缀操作次数**
|
||||||
|
- 定义 `pre_ops[i]` 表示前 $ i $ 个元素严格递增所需的操作次数。
|
||||||
|
- 定义 `suf_ops[i]` 表示后 $ N - i + 1 $ 个元素严格递减所需的操作次数。
|
||||||
|
|
||||||
|
2. **计算 `pre_ops` 和 `suf_ops`**
|
||||||
|
```cpp
|
||||||
|
vector<long long> pre(N+1, 0);
|
||||||
|
for(int i=2;i<=N;i++) {
|
||||||
|
pre[i] = max(pre[i-1] + 1 + (A[i-1] - A[i]), 0LL);
|
||||||
|
}
|
||||||
|
|
||||||
|
vector<long long> suf(N+1, 0);
|
||||||
|
for(int i=N-1;i>=1;i--) {
|
||||||
|
suf[i] = max(suf[i+1] + 1 + (A[i+1] - A[i]), 0LL);
|
||||||
|
}
|
||||||
|
|
||||||
|
vector<long long> pre_ops(N+1, 0);
|
||||||
|
for(int i=1;i<=N;i++) {
|
||||||
|
pre_ops[i] = pre_ops[i-1] + max(pre[i] - (i > 1 ? pre[i-1] : 0LL), 0LL);
|
||||||
|
}
|
||||||
|
|
||||||
|
vector<long long> suf_ops(N+1, 0);
|
||||||
|
for(int i=N;i>=1;i--) {
|
||||||
|
suf_ops[i] = suf_ops[i+1] + max(suf[i] - (i < N ? suf[i+1] : 0LL), 0LL);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **结合前缀和后缀操作次数**
|
||||||
|
- 对于每个 $ k $,总操作次数为 `pre_ops[k] + suf_ops[k+1]`
|
||||||
|
- 需要遍历所有 $ k $ 找到最小值。
|
||||||
|
|
||||||
|
4. **找到最小操作次数**
|
||||||
|
```cpp
|
||||||
|
long long min_operations = LLONG_MAX;
|
||||||
|
for(int k=1;k<=N;k++) {
|
||||||
|
long long total_ops = pre_ops[k] + suf_ops[k+1];
|
||||||
|
min_operations = min(min_operations, total_ops);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
这样,整个过程的时间复杂度降为 $ O(N) $,适用于题目给定的约束。
|
||||||
|
|
||||||
|
### 示例解析
|
||||||
|
|
||||||
|
以样例输入1为例:
|
||||||
|
|
||||||
|
```
|
||||||
|
5
|
||||||
|
3 2 2 3 1
|
||||||
|
```
|
||||||
|
|
||||||
|
**递增部分的增量 (`pre` 数组):**
|
||||||
|
- `pre[1] = 0`
|
||||||
|
- `pre[2] = max(0 + 1 + (3 - 2), 0) = 2`
|
||||||
|
- `pre[3] = max(2 + 1 + (2 - 2), 0) = 3`
|
||||||
|
- `pre[4] = max(3 + 1 + (2 - 3), 0) = 3`
|
||||||
|
- `pre[5] = max(3 + 1 + (3 - 1), 0) = 5`
|
||||||
|
|
||||||
|
**递减部分的增量 (`suf` 数组):**
|
||||||
|
- `suf[5] = 0`
|
||||||
|
- `suf[4] = max(0 + 1 + (1 - 3), 0) = 0`
|
||||||
|
- `suf[3] = max(0 + 1 + (3 - 2), 0) = 2`
|
||||||
|
- `suf[2] = max(2 + 1 + (2 - 2), 0) = 3`
|
||||||
|
- `suf[1] = max(3 + 1 + (2 - 3), 0) = 4`
|
||||||
|
|
||||||
|
**前缀操作次数 (`pre_ops`):**
|
||||||
|
- `pre_ops[1] = 0`
|
||||||
|
- `pre_ops[2] = 0 + max(2 - 0, 0) = 2`
|
||||||
|
- `pre_ops[3] = 2 + max(3 - 2, 0) = 3`
|
||||||
|
- `pre_ops[4] = 3 + max(3 - 3, 0) = 3`
|
||||||
|
- `pre_ops[5] = 3 + max(5 - 3, 0) = 5`
|
||||||
|
|
||||||
|
**后缀操作次数 (`suf_ops`):**
|
||||||
|
- `suf_ops[5] = 0`
|
||||||
|
- `suf_ops[4] = 0 + max(0 - 0, 0) = 0`
|
||||||
|
- `suf_ops[3] = 0 + max(2 - 0, 0) = 2`
|
||||||
|
- `suf_ops[2] = 2 + max(3 - 2, 0) = 3`
|
||||||
|
- `suf_ops[1] = 3 + max(4 - 3, 0) = 4`
|
||||||
|
|
||||||
|
**计算每个 $ k $ 的总操作次数:**
|
||||||
|
- $ k = 1 $: `pre_ops[1] + suf_ops[2] = 0 + 3 = 3`
|
||||||
|
- $ k = 2 $: `pre_ops[2] + suf_ops[3] = 2 + 2 = 4`
|
||||||
|
- $ k = 3 $: `pre_ops[3] + suf_ops[4] = 3 + 0 = 3`
|
||||||
|
- $ k = 4 $: `pre_ops[4] + suf_ops[5] = 3 + 0 = 3`
|
||||||
|
- $ k = 5 $: `pre_ops[5] + suf_ops[6] = 5 + 0 = 5`
|
||||||
|
|
||||||
|
最小操作次数为3。
|
||||||
|
|
||||||
|
### 实现代码
|
||||||
|
|
||||||
|
以下是基于上述思路的C++实现:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include <bits/stdc++.h>
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
typedef long long ll;
|
||||||
|
|
||||||
|
int main(){
|
||||||
|
ios::sync_with_stdio(false);
|
||||||
|
cin.tie(0);
|
||||||
|
int N;
|
||||||
|
cin >> N;
|
||||||
|
vector<ll> A(N+1, 0);
|
||||||
|
for(int i=1;i<=N;i++) cin >> A[i];
|
||||||
|
|
||||||
|
// Compute pre[i]
|
||||||
|
vector<ll> pre(N+1, 0);
|
||||||
|
pre[1] = 0;
|
||||||
|
for(int i=2;i<=N;i++){
|
||||||
|
pre[i] = max(pre[i-1] + 1 + (A[i-1] - A[i]), 0LL);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute suf[i]
|
||||||
|
vector<ll> suf(N+2, 0); // suf[N+1] = 0
|
||||||
|
for(int i=N;i>=1;i--){
|
||||||
|
if(i == N){
|
||||||
|
suf[i] = 0;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
suf[i] = max(suf[i+1] + 1 + (A[i+1] - A[i]), 0LL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute pre_ops[i]
|
||||||
|
vector<ll> pre_ops(N+1, 0);
|
||||||
|
pre_ops[0] = 0;
|
||||||
|
pre_ops[1] = max(pre[1] - 0, 0LL);
|
||||||
|
for(int i=2;i<=N;i++){
|
||||||
|
pre_ops[i] = pre_ops[i-1] + max(pre[i] - pre[i-1], 0LL);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute suf_ops[i]
|
||||||
|
vector<ll> suf_ops(N+2, 0); // suf_ops[N+1] = 0
|
||||||
|
suf_ops[N+1] = 0;
|
||||||
|
suf_ops[N] = max(suf[N] - 0, 0LL);
|
||||||
|
for(int i=N-1;i>=1;i--){
|
||||||
|
suf_ops[i] = suf_ops[i+1] + max(suf[i] - suf[i+1], 0LL);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find minimum operations over all k
|
||||||
|
ll min_operations = LLONG_MAX;
|
||||||
|
for(int k=1;k<=N;k++){
|
||||||
|
ll total_ops = pre_ops[k] + suf_ops[k+1];
|
||||||
|
min_operations = min(min_operations, total_ops);
|
||||||
|
}
|
||||||
|
// Edge case when k=N
|
||||||
|
// When k=N, suf_ops[k+1] = suf_ops[N+1] =0
|
||||||
|
cout << min_operations;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 复杂度分析
|
||||||
|
|
||||||
|
- **时间复杂度**: $ O(N) $,每个预处理步骤都是线性扫描。
|
||||||
|
- **空间复杂度**: $ O(N) $,需要存储前缀和后缀的增量及操作次数。
|
||||||
|
|
||||||
|
### 总结
|
||||||
|
|
||||||
|
通过预先计算递增和递减部分的增量需求,并结合这些需求计算最少的连续子数组操作次数,我们能够高效地解决这道题。关键在于将问题分解为前缀和后缀两个部分,并利用动态规划的方法优化计算过程,确保算法在大规模数据下依然高效。
|
1
src/20241120/array/array1.ans
Normal file
1
src/20241120/array/array1.ans
Normal file
@ -0,0 +1 @@
|
|||||||
|
3
|
2
src/20241120/array/array1.in
Normal file
2
src/20241120/array/array1.in
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
5
|
||||||
|
3 2 2 3 1
|
1
src/20241120/array/array2.ans
Normal file
1
src/20241120/array/array2.ans
Normal file
@ -0,0 +1 @@
|
|||||||
|
0
|
2
src/20241120/array/array2.in
Normal file
2
src/20241120/array/array2.in
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
5
|
||||||
|
9 7 5 3 1
|
1
src/20241120/array/array3.ans
Normal file
1
src/20241120/array/array3.ans
Normal file
@ -0,0 +1 @@
|
|||||||
|
1
|
2
src/20241120/array/array3.in
Normal file
2
src/20241120/array/array3.in
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
2
|
||||||
|
2024 2024
|
1
src/20241120/array/array4.ans
Normal file
1
src/20241120/array/array4.ans
Normal file
@ -0,0 +1 @@
|
|||||||
|
93
|
2
src/20241120/array/array4.in
Normal file
2
src/20241120/array/array4.in
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
8
|
||||||
|
12 2 34 85 4 91 29 85
|
1
src/20241120/array/array5.ans
Normal file
1
src/20241120/array/array5.ans
Normal file
@ -0,0 +1 @@
|
|||||||
|
16618723651088
|
2
src/20241120/array/array5.in
Normal file
2
src/20241120/array/array5.in
Normal file
File diff suppressed because one or more lines are too long
@ -1,9 +1,9 @@
|
|||||||
# NOIP2018 T3 题解
|
# NOIP2018提高组 T3 题解
|
||||||
>关于使用AI
|
>关于使用AI
|
||||||
>
|
>
|
||||||
>1. 代码由我书写,注释和解释由ai书写
|
>1. 代码由我书写,注释和解释由ai书写
|
||||||
>2. 文章经过了我的审核
|
>2. 文章经过了我的审核
|
||||||
>3. [博客食用更佳](https://blog.zziyu.cn/archives/noip2018-t3-ti-jie)
|
>3. [博客食用更佳](https://blog.zziyu.cn/archives/noip2018-s-t3-ti-jie)
|
||||||
|
|
||||||
## 问题描述
|
## 问题描述
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user