题解:P9780 [HUSTFC 2023] Azur Lane
P9780 [HUSTFC 2023] Azur Lane
题意:
给定一长度为 \(m\) 的序列 \(a\),分别将序列 \(a\) 分成 \(1,2, \dots ,n\) 段(\(n\) 不确定,每段都不为空),要求每段内的数字单调不上升,定义代价为 \(\sum\limits_{i=1}^{k}cut[i]\)(\(k\) 表示分成的段数,\(cut[i]\) 表示前 \(i\) 段内数字个数),输出分成 \(1,2, \dots ,n\) 段各自所需要的最小代价,若无解输出 \(-1\)。
思路:
显然,\(1 \leq n \leq m\)。
- 当 \(n=m\) 时,每个数字单独成段,代价恒为 \(\frac{(1+n) \times n}{2}\)(等差数列求和)。
- 当 \(n < m\) 时,由贪心得:一个数字越往后放越优(被重复计算的次数少)。那么考虑倒序枚举数字大小,若当前数字不小于后面数字,则将它加入后面数字的那一段使段数减小,并计算该段数代价,由贪心得,该代价为最优解。
- 代码有注释,如果不理解可以手动模拟看下。
数据范围:
\(1 \leq m \leq 10^6\),求和会爆 int,要开 long long。
代码:
#include<bits/stdc++.h>
using namespace std;
long long m, k, a[1000005];
long long f[1000005]; //f[i] 表示分成 i 段时需要的代价
int main(){
ios::sync_with_stdio(0);
cin >> m >> k;
for(int i=1; i<=m; i++) cin >> a[i];
f[m]=(1+m)*m/2; //当 n=m 时的代价
long long k=m; // k 表示当前的分段数量
for(int i=m-1; i>=1; i--){
if(a[i]>=a[i+1]) {
k--; //可以将 a[i] 与 a[i+1] 捆绑,因此分段数量减少 1
f[k]=f[k+1]-i; //减去该数字在上一个代价中的贡献
}
}
for(int i=1; i<=m; i++){
if(!f[i]) cout << "-1 ";
else cout << f[i] << " ";
}
return 0;
}

浙公网安备 33010602011771号