[DP]序列游戏

题目描述

这题由于删除了一个序列之后两端会合并,所以说如果DP的状态设置的不好的话会有后效性。
对于区间[l,r],如果r和之后的k个数连成了序列的话,那么[l,r-1]的元素就和后面要合并的k个元素就没有关系了。
用f[i][j][k]表示j和后面的k个数一起删除,i到j的区间删空能得到的最大价值。
f取出来的数在已知的部分i-j是
有峰的,发现直接用f转移有状态转移不全,再维护一个已知部分i-j递增没峰的。
然后考虑转移:
1、j和后面k个数直接合并。

2、j连到了[i,j-1]中的某个数,设这个数的位置为p,所以我们要考虑val[p]和val[j]的大小,发现有两种情况。val[p]>val[j]时,f[i][j][k]可以从g[i][p][k+1]+f[p+1][j-1][0]转移,表示p是峰或者从f[i][p][k+1]+f[p+1][j-1][0]转移,表示p之前还有峰。当val[p]<val[j]时,则必须限制j是当前的峰,所以用g转移。

3、g的转移和f类似,但只能从val[p]<val[j]转移过来,表示峰变高了。

4、由于这里的dp都是把题目消光的,于是我们再用一个n^2的dp,往优了取。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
const int N=160;
int x,n,cnt,p;
int ans[N];
int last[N],pre1[N],pre2[N],pre3[N],b[N],g[N][N][N],f[N][N][N],val[N];
std::map<int,int> hash;
int main()
{
    freopen("sequence.in","r",stdin);
    freopen("sequence.out","w",stdout);
    std::cin>>n;
    for(int i=1;i<=n;++i)
        std::cin>>val[i];
    for(int i=1;i<=n;++i)
    {
        std::cin>>b[i];
        if(!hash[b[i]]) hash[b[i]]=++cnt;
    }
    for(int i=1;i<=n;++i)
    {
        pre1[i]=last[hash[b[i]-1]];
        pre2[i]=last[hash[b[i]+1]];
        pre3[i]=last[hash[b[i]]];
        last[hash[b[i]]]=i;
    }
    memset(f,-127/3,sizeof(f));
    memset(g,-127/3,sizeof(f));
    for(int i=1;i<=n;++i)
    {
        for(int j=0;j<=n;++j)
        if(i+j<=n) f[i][i][j]=g[i][i][j]=val[j+1];
        else break;
        f[i][i-1][0]=g[i][i-1][0]=0;
    }
    for(int l=1;l<=n;++l)
    for(int i=1;i<=n;++i)
    {
        int j=i+l;
        if(j>n) break;
        for(int k=0;k<=n;++k)
        {
            if(j+k>n) break;
            f[i][j][k]=std::max(f[i][j][k],f[i][j-1][0]+val[1+k]);
            g[i][j][k]=std::max(g[i][j][k],f[i][j-1][0]+val[1+k]);
            p=pre1[j];
            while(p&&p>=i)
            {
                g[i][j][k]=std::max(g[i][p][k+1]+f[p+1][j-1][0],g[i][j][k]);
                f[i][j][k]=std::max(g[i][p][k+1]+f[p+1][j-1][0],f[i][j][k]);
                p=pre3[p];
            }
            p=pre2[j];
            while(p&&p>=i)
            {
                f[i][j][k]=std::max(g[i][p][k+1]+f[p+1][j-1][0],f[i][j][k]);
                f[i][j][k]=std::max(f[i][p][k+1]+f[p+1][j-1][0],f[i][j][k]);
                p=pre3[p];
            }
//          printf("%d %d %d %d %d\n",i,j,k,g[i][j][k],f[i][j][k]);
        }
    }
    for(int i=1;i<=n;++i)
    {
        ans[i]=ans[i-1];
        for(int j=0;j<i;++j)
        ans[i]=std::max(ans[i],ans[j]+f[j+1][i][0]);
    }
    printf("%d",ans[n]);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值