zoukankan      html  css  js  c++  java
  • 2020阿里3.20笔试题

    这一次我没有参加,听别人说是两个DP,然后我还是想了好久。

    第一题:有n段字符串,每串中的字符都是非递减的,现可以将它们拼接,求最长的非递减序列。其中 $1 leq n leq 10^6$,字符串的总长度不超过1e6且都由小写字母组成。

    分析:既然是DP,如果按前i个考虑,必定要排序,1e6肯定超时,所以要从另一个角度定义状态,观察到全都是小写字母,非递减,所以定义dp[i][j]为以第i个字母开始、第j个字母结束的最长非递减序列。

    #include<bits/stdc++.h>
    using namespace std;
    
    const int maxn = 1e6 + 10;
    int dp[26][26], n;
    char str[maxn];
    
    void update()
    {
        int e1 = str[0] - 'a';
        int e2 = str[strlen(str)-1] - 'a';
        int len = strlen(str);
        for(int i = 0;i <= e1;i++)
            for(int j = 25;j >= e2;j--)
            {
                if(e1 == e2) // 插入
                    dp[i][j] = dp[i][j] + len;
                else   // 拼接
                    dp[i][j] = max(dp[i][j], dp[i][e1] + len + dp[e2][j]);
            }
    }
    
    int main()
    {
        scanf("%d", &n);
        for(int i = 0;i < n;i++)
        {
            scanf("%s", str);
            update();
        }
        printf("%d
    ", dp[0][25]);
    }

    第二题:有一副扑克牌,其中A, 2, 3, ..., 10各4张,A代表1。现在可以按一下方式打出牌:

    • 单牌:一张牌。例如3
    • 对子:数字相同的两张牌。例如77
    • 顺子:数字连续的五张牌。例如A2345

    输入10个整数,表示每张牌的个数。输出打光手中所有牌需要的最少次数。

    例如1 1 1 2  2 2 2 2 1 1,最少3次。

    分析:最开始觉得10维DP有点夸张,就想着用搜索+剪枝,

    #include<bits/stdc++.h>
    using namespace std;
    
    const int INF = 0x3f3f3f3f;
    int a[10];
    int min_times = INF;
    
    void dfs(int times)
    {
        //for(int i = 0;i < 10;i++)  printf("%d ", a[i]);
        //printf("
    ");
    
        // 递归出口
        bool flag = true;
        for(int i = 0;i < 10;i++)
            if(a[i])
            {
                flag = false;
                break;
            }
        if(flag)
        {
            if(times < min_times)  min_times = times;
            return;
        }
    
        if(times+1 >= min_times)  return;   // 剪枝
    
        // 优先出顺子
        for(int i = 0;i < 6;i++)
        {
            if(a[i] && a[i+1] && a[i+2] && a[i+3] && a[i+4])
            {
                for(int j = 0;j < 5;j++)  a[i+j]--;
                dfs(times+1);
                for(int j = 0;j < 5;j++)  a[i+j]++;
            }
        }
    
        // 再考虑出对子
        for(int i = 0;i < 10;i++)
        {
            if(a[i] >= 2)
            {
                a[i] -= 2;
                dfs(times+1);
                a[i] += 2;
            }
        }
    
        // 出一张牌
        for(int i = 0;i < 10;i++)
        {
            if(a[i] >= 1)
            {
                a[i]--;
                dfs(times+1);
                a[i]++;
            }
        }
    }
    
    int main()
    {
        for(int i = 0;i < 10;i++)  scanf("%d", &a[i]);
        dfs(0);
        printf("%d
    ", min_times);
    
        return 0;
    }

    我试了大点的次数会超时,于是改成记忆化搜索,快了许多(果真10维DP。。。)

    #include<bits/stdc++.h>
    using namespace std;
    
    const int INF = 0x3f3f3f3f;
    int a[10];
    int min_times = INF;
    int d[5][5][5][5][5][5][5][5][5][5];
    
    int dp()
    {
        //for(int i = 0;i < 10;i++)  printf("%d ", a[i]);
        //printf("
    ");
    
        int& res = d[a[0]][a[1]][a[2]][a[3]][a[4]][a[5]][a[6]][a[7]][a[8]][a[9]];
        if(res != 0)  return res;
        res = INF;
    
        bool flag = true;
        for(int i = 0;i < 10;i++)  // 判断是否为全0
            if(a[i])
            {
                flag = false;
                break;
            }
        if(flag)
        {
            return res = 0;
        }
    
    
        // 优先出顺子
        for(int i = 0;i < 6;i++)
        {
            if(a[i] && a[i+1] && a[i+2] && a[i+3] && a[i+4])
            {
                for(int j = 0;j < 5;j++)  a[i+j]--;
                int tmp = dp()+1;
                if(tmp < res)  res = tmp;
                for(int j = 0;j < 5;j++)  a[i+j]++;
            }
        }
    
        // 再考虑出对子
        for(int i = 0;i < 10;i++)
        {
            if(a[i] >= 2)
            {
                a[i] -= 2;
                int tmp = dp()+1;
                if(tmp < res)  res = tmp;
                a[i] += 2;
            }
        }
    
        // 出一张牌
        for(int i = 0;i < 10;i++)
        {
            if(a[i] >= 1)
            {
                a[i]--;
                int tmp = dp()+1;
                if(tmp < res)  res = tmp;
                a[i]++;
            }
        }
        return res;
    }
    
    
    int main()
    {
        for(int i = 0;i < 10;i++)  scanf("%d", &a[i]);
        int ans = dp();
        printf("%d
    ", ans);
    
        return 0;
    }

    时间复杂度:状态数O(5^10) * 每个状态的转移次数O(10+6+10) = 2.5e8,没问题。

    参考链接:https://blog.csdn.net/qq_28597451/article/details/105005092

  • 相关阅读:
    vue table 固定首列和首行
    手机号隐藏中间4位变成****
    微信小程序填坑之page[pages/XXX/XXX] not found.May be caused by
    table表格固定前几列,其余的滚动
    大文件切片功能
    js如何判断数字是否有小数
    获取当前时间前后6个月的时间数组
    为你的mail server增加SPF记录
    给hmailserver添加DKIM签名
    HttpWatch工具简介及使用技巧
  • 原文地址:https://www.cnblogs.com/lfri/p/12548052.html
Copyright © 2011-2022 走看看