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\n", 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("\n");

    // 递归出口
    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\n", 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("\n");

    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\n", ans);

    return 0;
}

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

 

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

posted @ 2020-03-22 20:22  Rogn  阅读(5088)  评论(2编辑  收藏  举报