HDU6357——Hills And Valleys

点击打开原题目

题意:给一串由n个数字组成的字符串,选择其中一个区间进行翻转,要求翻转后该字符串的最长非降子序列长度最长,输出这个最长非降子序列的长度以及翻转的区间的左右端点。

题解:由于n的大小为1e5,如果直接枚举a中的翻转位置的话,那么复杂度肯定不行,但是这里有一种十分巧妙的做法。首先如果是求一个只有数字的串中的最长上升子序列长度的话,那就是这个串与 "0123456789" 这个串的最长公共子序列。而要求最长非降的话,那就是两个串可以重复匹配,遇到相同的位置答案依然加1。有了这个预备知识,我们就可以做这道题了,因为题目上给了字符串中的元素是0~9中的,所以如果翻转后可以是升序的话,那翻转前就是存在降序了,所以我们设原字符串为a,b字符串为:"0123456789",不断枚举b数组中的所有区间被翻转的情况,然后找a数组与b数组的最长公共子序列就是答案了。为什么?因为b没翻转前就是一个递增的序列,翻转之后就有了一个性质:只要再反转一次就能变成递增的序列。但是需要注意的是题目要求的是非递减,所以当翻转b的时候要额外保留翻转的两端点在原位置。由于还要求输出翻转的左右端点,所以额外开两个二维数组分别保存左端点和右端点。

附上代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 100005;
const int maxm = 22;
int n, a[maxn], dp[maxn][maxm], b[maxm];
int ans, ansl, ansr, l, r, cnt;
int al[maxn][maxm], ar[maxn][maxm];
char s[maxn];

int solve()
{
    for(int i = 0; i <= cnt; i++)
        dp[0][i] = 0;
    for(int i = 1; i <= n; i++)
    {
        for(int j = 1; j <= cnt; j++)
        {
            dp[i][j] = dp[i - 1][j];
            al[i][j] = al[i - 1][j];//al记录翻转的左端点
            ar[i][j] = ar[i - 1][j];//al记录翻转的左端点
            if(a[i] == b[j])
            {
                dp[i][j] = dp[i - 1][j] + 1;
                if(l == j && !al[i][j])//如果当前的j就是b开始翻转的左端点,更新记录
                    al[i][j] = i;
                if(r == j)//当前的j是b翻转的右端点,记录更新
                    ar[i][j] = i;
            }
            if(dp[i][j - 1] > dp[i][j])//如果答案有更新就要更新答案
            {
                dp[i][j] = dp[i][j - 1];
                al[i][j] = al[i][j - 1];
                ar[i][j] = ar[i][j - 1];
            }
        }
    }
    return dp[n][cnt];
}

int main()
{
    int t;
    scanf("%d", &t);
    while(t--)
    {
        scanf("%d%s", &n, s + 1);
        for(int i = 1; i <= n; i++)
            a[i] = s[i] - '0';
        cnt = 0;
        for(int i = 0; i <= 9; i++)
            b[++cnt] = i;
        ansl = ansr = l = r = 1;
        ans = solve();
        for(int i = 0; i <= 9; i++) //枚举翻转b数组的每一段
        {
            for(int j = i + 1; j <= 9; j++)
            {
                cnt = 0;
                for(int k = 0; k <= i; k++)
                    b[++cnt] = k;
                l = cnt+1;//左端点
                for(int k = j; k >= i; k--)//只翻转i~j区间的数
                    b[++cnt] = k;
                r = cnt;//右端点
                for(int k = j; k <= 9; k++)
                    b[++cnt] = k;
               /* for(int i=1; i<=cnt; i++)
                    printf("%d ",b[i]);
                printf("\n");*/
                int tmp = solve();
                if(ans < tmp && al[n][cnt] && ar[n][cnt])//不断更新答案
                {
                    ans = tmp;
                    ansl = al[n][cnt], ansr = ar[n][cnt];
                }
            }
        }
        printf("%d %d %d\n", ans, ansl, ansr);
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/wookaikaiko/article/details/81604558
今日推荐