2018 Multi-University Training Contest 5 HDU - 6357 Hills And Valleys

bryce1010模板
https://vjudge.net/problem/HDU-6357
题意:
求最长不下降子序列,支持一次翻转区间操作。
思路:
将数组b设为0-9的子序列,那么a和b的最长上升子序列就是a的最长不下降子序列。
对于翻转区间操作,可以转换为对b序列的翻转操作,这样就只有C(10,2)中选择。
如果不太明白可以看下面这句摘抄的话。

它把最长不下降子序列映射成两个序列的最长公共子序列问题 
a序列就是给出的原序列 
b序列是值域的序列 
需要注意的是:b序列可以重复匹配

一般的最长不下降子序列中,b序列就是:0,1,2,3,4,5,6,7,8,9 
这样a和b的最长公共子序列就是一个最长不下降子序列。

然后这题它说可以翻转一次。我们发现如果在a序列中枚举翻转端点是很难实现的。但可以在b序列上枚举翻转端点(最多C(10,2)种方案)。 
换句话说,我们可以枚举翻转的两个端点的值。 
然后,b序列可以转化成这个样子: 
假设我们枚举的翻转的左端点值为y,右端点值为x,满足x<y 
b序列就可以变成: 
0,1,2,……x−1,x,(y,y−1,y−2,……,x+1,x),y,y+1,……8,9其中括号内的部分可以通过翻转使得整个串仍然是一个09的不下降序列。

——引用https://blog.csdn.net/qq_34454069/article/details/81475646

#include <bits/stdc++.h>

using namespace std;
#define ll long long


const int MAXN=2e5+10;
int a[MAXN];
int b[MAXN];
char s[MAXN];

int n;
int dp[MAXN][22],tl[MAXN][22],tr[MAXN][22];
int spl,spr;//交换的两个位置
int ansl,ansr;

//求最长公共子序列部分
//dp[i][j]表示a的第i个和b的第j个

int solve(int cnt)
{
    for(int i=0;i<cnt;i++)
        dp[0][i]=0;
    for(int i=1;i<=n;i++)
    {
        for(int j=0;j<cnt;j++)
        {
            dp[i][j]=dp[i-1][j];
            tl[i][j]=tl[i-1][j];
            tr[i][j]=tr[i-1][j];

            if(s[i]==b[j])
            {
                dp[i][j]++;
                if(spl==j&&tl[i][j]==0)
                    tl[i][j]=i;
                if(spr==j)
                    tr[i][j]=i;
            }
            if(dp[i][j-1]>dp[i][j])
            {
                dp[i][j]=dp[i][j-1];
                tl[i][j]=tl[i][j-1];
                tr[i][j]=tr[i][j-1];
            }


        }
    }
    return dp[n][cnt-1];
}



int main()
{
//    freopen("1008.in","r",stdin);
    //b[i]是0-9 的序列
    //求a的最长不下降子序列就是求a和b的最长公共子序列
    //对于翻转操作,也可以转换成对b 序列的翻转操作
    int t;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d",&n);
        scanf("%s",s+1);
        //先比较0-9的序列
        int minl=9,maxl=0;
        for(int i=1;i<=n;i++)
        {
            s[i]=s[i]-'0';
            maxl=max(maxl,(int)s[i]);
            minl=min(minl,(int)s[i]);
        }
        //先跟0-9的序列求
        for(int i=0;i<10;i++)
            b[i]=i;
        int ans=solve(10);

        ansl=ansr=1;
        //任选两个位置
        for(int l=minl;l<=maxl;l++)
        {
            for(int r=minl;r<l;r++)
            {
                int cnt=0;
                for(int i=0;i<=r;i++)
                    b[cnt++]=i;
                spl=cnt;
                for(int i=l;i>=r;i--)
                {
                    b[cnt++]=i;
                }
                spr=cnt-1;
                for(int i=l;i<10;i++)
                    b[cnt++]=i;
                int ans1=solve(cnt);
                if(ans1>ans&&tl[n][cnt-1]&&tr[n][cnt-1])
                {
                    ans=ans1;
                    ansl=tl[n][cnt-1];
                    ansr=tr[n][cnt-1];
                }



            }
        }
        printf("%d %d %d\n",ans,ansl,ansr);





    }


    return 0;
}

猜你喜欢

转载自blog.csdn.net/Fire_to_cheat_/article/details/81482860