The 2022 ICPC Asia Regionals Online Contest (II) B Non-decreasing Array

传送门:题目详情 - B Non-decreasing Array (pintia.cn)

 题意:非减序列,每次进行两步操作:

  1. 删除一个数或不做操作
  2. 修改一个数到任意值

保证修改完后仍是非减,求操作次数1~n时\sum_{i = 2}^n {(a_i - a_{i-1})^2}最大值分别是多少?

比赛一看题高兴坏了,想着100的数据规模正好对应0.4s的时间,跟队友说贪心一波过,然后就开头改到结尾😭

所以这个题是没有局部最优推全局最优的,100的规模果然是dp捏。

首先很快可以想到修改操作等价于删除,所以操作次数2*k大于等于n-2时答案固定为(a_1 - a_n)^2.

至于中间……

很快会想到区间dp,复杂度就hin像,但是需要一些魔改,例如区间dp一定是取一段连续区间,在本题中则是多段连续区间

f[l][r][k]表示l到r区间,两端必不删,操作k次,取得的最大值。

状态转移为

f[l][r][k] = \begin{cases} f[l][r-1][k] + (a_r - a_{r-1})^2 & \text{$a_{r-1}$ exsits} \\ \max_{j=l+1}^{r-1} f[j][r][r-j-1] + f[l][j-1][k-(r-j-1)] + (a_{j} - a _{j-1})^2 & \text{ $a_{r-1}$ is deleted AND $k >= r-j-1$} \end{cases}

与区间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;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值