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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM