• 2016级算法期末模拟练习赛-F.AlvinZH的青春记忆IV


    1086 AlvinZH的青春记忆IV

    思路

    难题,动态规划。

    这是一道很有意思的题,因为它不仅卡了时间,也卡了空间,而且卡的很妙很迷。

    光是理解题意已经有点难度,简化题意:两串数字序列,相等的数字定义为可以交战,若 AiBj,则 Ai 可与 Bj 交战,之后下一场 Aii、Bjj 交战的条件是:AiiBjj、ii>i、jj>j、Aii>Ai、Bjj>Bj 。

    这是什么呢?最长公共子序列?最长递增子序列?都很像,其实是最长公共递增子序列!最长公共子序列与最长递增子序列的叠加,即LCS+LIS,称为 LCIS 问题。

    如果你不知道怎么求LCS和LIS,请复习后继续往下看。

    在开始讲TLE问题前,先讲一下MLE问题。可以发现题目中所给的 (n) 最大有1e4,如果你的(dp)数组是int的二维1e4数组,明显已经超过空间限制。
    这里想一想 (dp) 数组的定义,它代表的是LCIS的大小,这个大小是不可能超过 (n)(m) 的,所以dp[i][j]<1e4,所以dp数组定义为 (short int)
    注意一个小问题:数学函数max(int,int),比较大小前需要转成int。

    方法一:最原始的方法——完全暴力

    (dp[i][j]):A前i个元素和B前j个元素最长公共上升子序列。

    四重for循环,外层两个判断LCS,内层两个判断LIS。TLE,不用说。

    代码如下,时间复杂度:O(n4),空间复杂度:O(n2)。可得0.4分,TLE。

    int n, m, ans;
    int A[10005];
    int B[10005];
    short int dp[10005][10005];//dp[i][j]:A前i个元素和B前j个元素最长公共上升子序列
    
    ans = 0;
    memset(dp, 0, sizeof(dp));
    
    for(int i = 1; i <= n; i++)
    {
        for(int j = 1; j <= m; j++)
        {
            if(A[i] == B[j])
            {
                for(int k = 0; k < i; k++)
                    if(A[k] < A[i])
                        for(int l = 0;l < j; l++)
                            dp[i][j] = max((int)dp[i][j], (int)dp[k][l]+1);
            }
            ans = max(ans, (int)dp[i][j]);
        }
    }
    
    printf("%d
    ", ans);
    
    //完全暴力
    //时间复杂度:O(n^4)
    //空间复杂度:O(n^2)
    

    方法二:推翻重来,重新定义DP数组

    可以发现,上一种方法中DP数组定义实在是太普通了,我们可以对其加以限制。

    (dp[i][j]):以A串的前i个整数与B串的前j个整数且以B[j]为结尾构成的LCIS的长度,即不强制选择A[i]且强制选择B[j]的长度。

    状态转移方程:

    • ①dp[i][j] = dp[i-1][j]。(A[i] != B[j])
    • ②dp[i][j] = max(dp[i-1][k])+1 (A[i] == B[j]) (1 <= k <= j-1 && B[j] > B[k])

    对于①,dp[i][j]定义可知:B[j]必须使用。但A[i] != B[j],A[i]无贡献,可直接取dp[i-1][j]。

    对于②:两数相等情况下为了最长,B[j]一定与A[i]匹配,若与之前某A[1i-1]匹配,一定没有比与A[i]匹配更优。接下来需要去找一个递增的以B[j]结尾的LCIS,枚举dp[i-1][1j-1]找最大值。为什么第一维是i-1,A[i]已经匹配,而取[1~i-2]显然没有比取i-1更优。

    参考代码一:最浅显的翻译

    根据上述dp定义可以直接翻译,得到最浅显的解决代码如下,依然超时,可得0.4分, TLE。

    时间复杂度:O(n3),空间复杂度:O(n2)。

    int n, m, ans;
    int A[10005];
    int B[10005];
    short int dp[10005][10005];//dp[i][j]:以A串的前i个整数与B串的前j个整数且以B[j]为结尾构成的LCIS的长度
    
    ans = 0;
    memset(dp, 0, sizeof(dp));
    
    for(int i = 1; i <= n; i++)
    {
        for(int j = 1; j <= m; j++)
        {
            if(A[i] == B[j])
            {
                int maxx = 0;
                for(int k = 1; k <= j-1; k++)//找到符合递增的最大长度
                {
                    if(B[j] > B[k])//保证递增
                        maxx = max(maxx, (int)dp[i-1][k]);
                }
                dp[i][j] = maxx + 1;
            }
            else
                dp[i][j] = dp[i-1][j];
        }
    }
    
    for(int j = 1; j <= m; j++)
      if(ans < dp[n][j]) ans = dp[n][j];
    
    //时间复杂度:O(n^3)
    //空间复杂度:O(n^2)
    

    参考代码二:代码一的时间优化

    时间优化:maxx可以在第二层循环直接求得,不需要第三层循环。如何求得?

    首先明白maxx的定义:maxx=max(dp[i-1][k]),k=[1,j-1]。指的是符合递增条件下(B[k]<B[j]),A[1~i-1]与B[1,j-1]形成的LCIS。

    由于B[j]已经与A[i]匹配,要找的是B[j] > B[k],即推出A[i] > B[k]。在第二层循环中直接判断即可更新maxx的值。

    代码如下,时间复杂度:O(n2),空间复杂度:O(n2)。可得0.6分,TLE。

    int n, m, ans;
    int A[10005];
    int B[10005];
    short int dp[10005][10005];//dp[i][j]:以A串的前i个整数与B串的前j个整数且以B[j]为结尾构成的LCIS的长度
    
    ans = 0;
    memset(dp, 0, sizeof(dp));
    
    for(int i = 1; i <= n; i++)
    {
        int maxx = 0;
        for(int j = 1; j <= m; j++)
        {
            if(A[i] == B[j])
                dp[i][j] = maxx+1;
            else
                dp[i][j] = dp[i-1][j];
    
            if(A[i] > B[j])
                maxx = max(maxx, (int)dp[i-1][j]);
        }
    }
    
    for(int j = 1; j <= m; j++)
        if(ans < dp[n][j]) ans = dp[n][j];
    
    //时间复杂度:O(n^2)
    //空间复杂度:O(n^2)
    

    参考代码三:对代码二的空间优化

    发现dp更新时只与上一状态有关,所以dp数组可以变成dp[2][10005],位操作滚动更新,其他不变。

    代码如下,时间复杂度:O(n^2),空间复杂度:O(n)。可得1分,AC。终于可以AC了,但是,为什么呢?

    莫名AC了,应该问问为什么。代码二与代码三对比,只是空间比小了,对于第二组数据,为什么一个1500ms直接T,而这个36ms刷的过去了呢?第二组数据有什么坑?

    我直说:第二组数据中n很小,Ai和Bi很小,不过数据组数有点大。一个原因可以理解为初始化问题,第二个原因是堆空间分配问题,看你们计组学的如何了!

    int n, m, ans;
    int A[10005];
    int B[10005];
    short int dp[2][10005];
    
    ans = 0;
    memset(dp, 0, sizeof(dp));
    
    int cur = 0;
    for(int i = 1; i <= n; i++)
    {
        cur = cur^1;
        int maxx = 0;
        for(int j = 1; j <= m; j++)
        {
            if(A[i] == B[j])
                dp[cur][j] = maxx+1;
            else
                dp[cur][j] = dp[cur^1][j];
    
            if(A[i] > B[j])
                maxx = max(maxx, (int)dp[cur^1][j]);
        }
    }
    
    for(int j = 1; j <= m; j++)
        if(ans < dp[cur][j]) ans = dp[cur][j];
    printf("%d
    ", ans);
    
    //时间复杂度:O(n^2)
    //空间复杂度:O(n)
    

    参考代码四:对代码三进一步优化

    什么!!!还可以优化!

    是的,其实既然可以位操作滚动优化,有可能可以像01背包一样直接变为一维数组。这里确实可以这样做。

    dp数组的定义稍微有所改变,dp[i]表示以B[i]结尾的LCIS,A的范围是[1~n]。

    代码如下,时间复杂度:O(n^2),空间复杂度:O(n)。可得1分,AC。

    int n, m, ans;
    int A[10005];
    int B[10005];
    short int dp[10005];//dp[i]表示以B[i]结尾的LCIS,A的范围是[1~n]
    
    ans = 0;
    memset(dp, 0, sizeof(dp));
    for(int i = 1; i <= n; i++)
    {
        int maxx = 0;
        for(int j = 1; j <= m; j++)
        {
            if(A[i] == B[j])
                dp[j] = maxx+1;
    
            if(A[i] > B[j])
                maxx = max(maxx, (int)dp[j]);
        }
    }
    
    for(int j = 1; j <= m; j++)
        if(ans < dp[j]) ans = dp[j];
    
    //时间复杂度:O(n^2)
    //空间复杂度:O(n)
    

    分析

    不分析了,还分析个毛线!只能说,我们对DP的力量一无所知!

  • 相关阅读:
    腾讯的这款产品,让我不禁打了个冷颤
    奇了,结婚也能写成区块链智能合约
    每个人都在经历淘宝的“大数据杀熟”,这5个办法巧妙避开
    云存储的未来:Scale Up还是Scale Out?
    storj白皮书v3最全面解读,Docker创始人的加入能否扳倒AWS S3
    一切为了解决隐私问题,绿洲实验室Ekiden协议介绍
    为什么去中心化存储也能保证数据不丢失?
    Vue调试辅助神器
    Spring Boot Admin 使用的坑
    Beyond Compare比较文件夹内容
  • 原文地址:https://www.cnblogs.com/AlvinZH/p/8137738.html
Copyright © 2020-2023  润新知