2024牛客暑期多校训练营2 I题(递归实现)

题目说明:

给定一个数组,长度为2*n,数组中的[1~n]的元素均会出现两次。

给定一个操作:

每次可以选择相同的数字x,然后将相同数字对应的区间(长度为len)删去,得到x*len的得分,该操作可以无限执行,问:最终可以得到的最大得分是多少。

解法:因为每个数字恰好都有两个,我们可以将每对数字抽象成一个区间(下标从1开始)

例如[1,2,3,1,2,3]数组

   1对应(1,4)

   2对应(2,5)

   3对应(3,6)

对应各个区间来说,共有3种情况:交叉、无关、包容

对于包容的情况:

   删除了更小的区间后,大区间依旧能够删除(首尾数字未变)。当小区间的端点数字更小时,删除小区间实际上最终得分会更低。

对于交叉情况:

   因为各区间存在互斥关系(即选取了某些区间,另一些区间就不能够选取),通常这种情况我们都需要计算选取了该区间能够得到的最优解。

如下图(两个包容区间交叉):

我们设cal(L,R)表示区间[L,R]的最优解(没有用任何包容区间情况下,该数组的值均为num[R])

该种情况下,我们如何选取R3,得到最优解呢?

  因为R区间包含了R2区间与R3区间,我们希望在选取R3时,R2不要打扰到它

  我们可以先再设一个数组g[i],表示[L,i]区间上 所有包容区间的右端点r 落在[L,i]中得到的[L,R]区间最优解

  计算当前的g[R3]时,我们仅需计算g[R3]=g[L3-1]+cal(L3,R3)-(R3-L3)*(num[R]),这样就不会被交叉的区间打扰

(后面那段需要减(R3-L3)*(num[R],因为g数组表示的已经是区间最优解了,计算了包容区间的最优解后,还需减去该区间原先的值)

最终的cal(L,R)返回 g[R-1] 即可 (减1因为R端点会占一个位置)

并且为了重复计算(可能多次跑同一个cal(l,r))我们需要存储先前跑过的值。

最终得到cal(0,2*n+1)的值即可

#include<bits/stdc++.h>
#define maxn 2000005
#define ll long long 
#define inf 0x7fffffff
using namespace std;
ll n;
ll a[maxn];
struct P {
    int l, r, i;
}p[maxn];
struct MP {
    int left, num;
}R[maxn];//用于记录右端点为i时,左端点为R[i].left,对应的值为R[i].num
ll dp[6005], v[6005], g[6005];

inline ll cal(int l, int r) {
    if (v[r])return v[r];//跑过的值直接返回即可
    for (int i = l; i <= r; i++) {
        dp[i] = g[i];//需要先存下g值,最后要还原
        g[i] = R[r].num * (r - l + 1);//对g赋初值
    }
    for (int i = l + 1; i < r; i++) {
        g[i] = max(g[i], g[i - 1]);
        if (!R[i].left || R[i].left < l || R[i].num < R[r].num)continue;
        g[i] = max(g[i], g[R[i].left - 1] + cal(R[i].left, i) - (i - R[i].left + 1) * R[r].num);
    }
    v[r] = g[r-1];//存储先前跑过的值
    for (int i = l; i <= r; i++) {
        g[i] = dp[i];//还原g值
    }
    return v[r];
}
void solve() {
    cin >> n;
    for (int i = 1; i <= 2 * n; i++) {
        cin >> a[i];
        if (!p[a[i]].l)p[a[i]].l = i;
        else p[a[i]].r = i;
        p[a[i]].i = a[i];
    }
    for (int i = 1; i <= n; i++) {
        R[p[i].r] = { p[i].l,p[i].i };
    }
    R[2 * n + 1] = { 0,0 };
    int i = 1;
    cout << cal(0, 2 * n + 1);
}

int main() {
    ios::sync_with_stdio(0);
    cin.tie(nullptr);
    cout.tie(nullptr);
    int t;
    t = 1;
    while (t--) {
        solve();
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值