传送门:题目详情 - B Non-decreasing Array (pintia.cn)
题意:非减序列,每次进行两步操作:
- 删除一个数或不做操作
- 修改一个数到任意值
保证修改完后仍是非减,求操作次数1~n时最大值分别是多少?
比赛一看题高兴坏了,想着100的数据规模正好对应0.4s的时间,跟队友说贪心一波过,然后就开头改到结尾😭
所以这个题是没有局部最优推全局最优的,100的规模果然是dp捏。
首先很快可以想到修改操作等价于删除,所以操作次数2*k大于等于n-2时答案固定为.
至于中间……
很快会想到区间dp,复杂度就hin像,但是需要一些魔改,例如区间dp一定是取一段连续区间,在本题中则是多段连续区间!
用表示l到r区间,两端必不删,操作k次,取得的最大值。
状态转移为
与区间dp的最大不同在于,区间dp在一段区间内操作次数是固定的,但本题中一段区间内操作次数可多可少,所以我们要对于每个操作次数都枚举区间,其余的枚举操作数、左端点和分割点的操作都与区间dp类似,注意枚举分割点时,要保证a[r-1]一定被删除,所以操作数要取那个区间能取的最大值。
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 105;
ll a[N], f[N][N][N], ans[N];
ll get(int l, int r){
return (a[l] - a[r]) * (a[l] - a[r]);
}
int main(){
int n;
cin >> n;
for(int i = 1; i <= n; i ++) cin >> a[i];
for(int i = 1; i < n ;i ++){
for(int j = i+1; j <= n ;j ++){
f[i][j][j-i-1] = get(i, j);
// cout << f[i][j][j-i-1] <<endl;
}
}
for(int k = 0; k <= n-2; k ++){ // 枚举操作数
for(int len = k+2; len <= n; len ++){ // 枚举区间长度
for(int l = 1; l <= n-len+1; l++){ // 枚举左端点
int r = l + len - 1;
f[l][r][k] = max(f[l][r-1][k] + get(r-1, r), f[l][r][k]);
for(int j = l+1; j < r; j ++){ // 枚举分割点
if(k >= r-j-1)
f[l][r][k] = max(f[l][r][k], f[j][r][r-j-1] + f[l][j-1][k-(r-j-1)] + get(j-1, j));
}
}
}
}
ll res = get(1, n);
for(int i = 2; i < n-2; i += 2){
cout << (ans[i] = max(ans[i-1], f[1][n][i])) <<endl;
}
for(int i = (n-1)/2 * 2; i <= 2*n; i += 2){
cout << res << endl;
}
return 0;
}
另附队友补题代码,看起来比我简单一些
#include<bitsdc++.h>
using namespace std;
using ll = long long;
int n;
ll a[105];
ll f[105][105];
int main()
{
cin >> n;
for(int i = 1; i <= n; i ++)
{
cin >> a[i];
}
for(int i = 2; i <= n; i ++)
{
for(int j = 0; j <= i - 2; j ++)
{
for(int k = 0; k <= j; k ++)
{
int pos = i - k - 1;
f[i][j] = max(f[i][j], f[pos][j - k] + (a[i] - a[pos]) * (a[i] - a[pos]));
}
}
}
for(int i = 1; i <= n; i ++)
{
if(i * 2 > n - 2) cout << f[n][n - 2] << '\n';
else cout << f[n][2 * i] << '\n';
}
return 0;
}