题目说明:
给定一个数组,长度为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();
}
}