[HNOI2009]双递增序列(动态规划,序列dp)

本文深入探讨了一种复杂的动态规划问题,通过巧妙的状态定义,解决了序列划分难题。关键在于使用dp[i][j]表示前i个数中,长度为j的第二序列的最小结尾值,实现了高效的状态转移。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

感觉这个题还蛮难想的.
首先状态特别难想.设\(dp[i][j]\)表示前i个数,2序列的长度为j的情况下,2序列的最后一个数的最小值.
其中1序列为上一个数所在的序列,2序列为另外一个序列.
这样设状态的巧妙之处在于,它几乎完美地用最精炼的语言描述了序列的信息,使我们可以方便地转移.我们现在知道1序列的最后一个数\(seq[i]\),2序列的最后一个数\(dp[i][j]\).1序列的长度\(i-j\),2序列的长度\(j\).
于是转移就是:
如果可以接在1序列上的话,即\(seq[i-1]<seq[i]\),那么\(dp[i][j]=min(dp[i][j],dp[i-1][j])\)
如果可以接在2序列上的话,即\(dp[i-1][i-j]<seq[i]\)(可以接在2序列上,但是由于状态是2序列的长度,所以两个序列要交换,所以是i-j),那么1序列和2序列要互换,此时2序列的最后一个元素就是之前1序列的最后一个元素\(seq[i-1]\),即\(dp[i][k]=min(dp[i][j],seq[i-1])\)

#include<bits/stdc++.h>
#define maxn 2005
using namespace std;
int seq[maxn],n;
int dp[maxn][maxn];
//dp[i][j]表示前i个数,2序列长度为j,2序列最后一个数的最小值.
int main()
{
    int T;cin>>T;
    while(T--)
    {
        scanf("%d",&n);
        for(int i=1;i<=n;i++)scanf("%d",&seq[i]);
        memset(dp,0x3f,sizeof(dp));dp[0][0]=-1;
        for(int i=1;i<=n;i++)
        {
            for(int j=0;j<=min(n/2,i);j++)
            {
                if(seq[i-1]<seq[i])dp[i][j]=dp[i-1][j];
                //大于前一个数,说明可以直接接在前一个数之后,2序列的最后元素不需要变大
                if(dp[i-1][i-j]<seq[i])dp[i][j]=min(dp[i][j],seq[i-1]);
                //大于2序列的最后一个数,说明可以接在2序列之后,此时1序列和2序列交换
                //本来作为数组里的值表示的现在变成下标体现,原本下标体现的现在变成值
                //1序列表示上一次的数所在的序列,2序列是另一个序列
                //seq[i-1]是1序列最后一个元素
            }
        }
        if(dp[n][n/2]<=1e6)printf("Yes!\n");
        else printf("No!\n");
    }
    return 0;
}

转载于:https://www.cnblogs.com/terribleterrible/p/9873856.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值