一个显然结论:(区间指两端相同的区间),如果一个两端数大的区间包含两端小的区间,那么小区间是没用的(直接用大区间取走小区间的所有卡片)
因此可以从大到小枚举区间,看这些区间内部的分配情况。
由于选区间的过程一定是区间套区间,不存在交叉,而写是小区间套大区间。因此容易想到dp,让小区间可以从大区间的答案转移过来。
设f[i]为端点为i的区间的最大值。
发现数据范围n<=3000,考虑直接依次从大到小枚举区间,再枚区间内部的点。定义dp[k]为区间内部从区间左端点到k范围内的答案最大值。如果i位某端点更大区间的右端点,则dp[i]<-dp[该区间左端点-1]+f[该区间](仅当左端点也在k区间内部)。记录一下每一个位置是否是右端点。同时,如果i点直接被k区间选走,那么dp[i]<-dp[i-1]+k
细节处理:dp数组需要重复使用,枚举区间时将dp[区间左端点-1]设为0,方便转移。
整个序列外面可以在套一个区间[0,0]收集答案。
#include<cstdio>
#include<iostream>
using namespace std;
typedef long long ll;
const ll N=3e3+10,M=N<<1;
ll l[N],r[N],f[N],a[M],dp[M]; bool vs[M];
int main(){
ll n; scanf("%lld",&n);
for(ll i=1;i<=n<<1;i++){
scanf("%lld",&a[i]);
if(l[a[i]]) r[a[i]]=i;
else l[a[i]]=i;
}
l[0]=1,r[0]=n<<1;
for(ll i=n;i>=0;i--){
dp[l[i]-1]=0;//no
for(ll j=l[i];j<=r[i];j++){
dp[j]=dp[j-1]+i;
if(a[j]>i&&r[a[j]]==j&&l[a[j]]>=l[i]){
dp[j]=max(dp[j],dp[l[a[j]]-1]+f[a[j]]);}
}
f[i]=dp[r[i]];
}
printf("%lld",f[0]);
return 0;
}