HDU 6357 Hills And Valleys(dp)

Description

给出一个长度为nn的数字串,可以将其中一段翻转,问翻转后该数字串的非严格最长上升子序列长度最大值

Input

第一行一整数T表示用例组数,每组用例输入一整数nn和一个长度为n的数字串

(1T100,1n105,n2105)(1≤T≤100,1≤n≤105,∑n≤2⋅105)

Output

输出翻转后该串的最长上升子序列长度最大值以及要翻转的区间端点

Sample Input

2
9
864852302
9
203258468

Sample Output

5 1 8
6 1 2

Solution

考虑反转部分两端点的值以及第一部分结束部分的值,以dp[i][x][y][z]dp[i][x][y][z]表示用前ii个数字构造且以ai结尾,使得第一部分结束值不超过xx,反转部分左端点值不超过z,右端点值不小于yy的前两部分最长长度,l[i][x][y][z]记录该状态最优解的中间反转部分左端点编号,那么每次aiai有两种情况,第一种是接到之前反转部分右边,那么此时aiai应该为yy,这样有转移dp[i][x][y][z]=dp[i1][x][y][z]+(ai=y),此时反转部分左端点不变,第二种是从aiai开始反转,那么11~i1这部分就成为第一部分,以L[i][x]L[i][x]表示前ii个数字以一个不超过x的数字结尾的最长上升子序列长度,以R[i][x]R[i][x]表示以ai,...,anai,...,an中若干字母组成以一个不小于xx的数字开始的最长上升子序列长度,那么此时有转移dp[i][x][y][z]=max(dp[i][x][y][z],L[i1][x]+(ai=y)),注意如果更新了dp[i][x][y][z]dp[i][x][y][z]要同步更新左端点l[i][x][y][z]=il[i][x][y][z]=i,在求出所有dp[i][x][y][z]dp[i][x][y][z]后,答案即为max(dp[i][x][y][z]+R[i+1][z])max(dp[i][x][y][z]+R[i+1][z])

Code

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define maxn 100005
int T,n,L[maxn][10],R[maxn][10],dp[2][10][10][10],l[2][10][10][10];
char s[maxn];
int main()
{
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d%s",&n,s+1);
        for(int i=1;i<=n;i++)s[i]-='0';
        memset(L[0],0,sizeof(L[0]));
        memset(R[n+1],0,sizeof(R[n+1]));
        for(int i=1;i<=n;i++)
            for(int j=0;j<=9;j++)
                L[i][j]=max(j>0?L[i][j-1]:0,L[i-1][j]+(s[i]==j));
        for(int i=n;i>=1;i--)
            for(int j=9;j>=0;j--)
                R[i][j]=max(j<9?R[i][j+1]:0,R[i+1][j]+(s[i]==j));
        memset(dp,0,sizeof(dp));
        memset(l,0,sizeof(l));
        int ans=0,ansl=1,ansr=1;
        for(int i=1;i<=n;i++)
        {
            for(int x=0;x<=9;x++)
                for(int z=x;z<=9;z++)
                    for(int y=z;y>=x;y--)
                    {
                        dp[i&1][x][y][z]=dp[(i-1)&1][x][y][z];
                        l[i&1][x][y][z]=l[(i-1)&1][x][y][z];
                        dp[i&1][x][y][z]+=(s[i]==y);
                        if(y<z&&dp[i&1][x][y][z]<dp[i&1][x][y+1][z])
                        {
                            dp[i&1][x][y][z]=dp[i&1][x][y+1][z];
                            l[i&1][x][y][z]=l[i&1][x][y+1][z];
                        }
                        if(dp[i&1][x][y][z]<L[i-1][x]+(s[i]==y))
                        {
                            dp[i&1][x][y][z]=L[i-1][x]+(s[i]==y);
                            l[i&1][x][y][z]=i;
                        }
                    }
                for(int x=0;x<=9;x++)
                    for(int z=x;z<=9;z++)
                        for(int y=z;y>=x;y--)
                            if(ans<dp[i&1][x][y][z]+R[i+1][z])
                            {
                                ans=dp[i&1][x][y][z]+R[i+1][z];
                                ansl=l[i&1][x][y][z],ansr=i;
                            }
        }
        printf("%d %d %d\n",ans,max(1,ansl),ansr);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值