CF49E Common ancestor 区间DP

文章目录

  • 一.题目
  • 二.题解
  • 三.Code
  • 谢谢!

一.题目

传送门
翻译:
Berland的每一个生物的DNA序列可以被表示成一个由小写字母组成的非空字符串。Berland的科学家们发现所有生物都是一步一步进化来的。在其中的每一步,DNA序列的一个字符会被替换成另外的两个。总共有nn种允许的变化。变化 a i → b i c i a_{i}\rightarrow b_{i}c_{i} aibici表示一个字符 a i a_i ai可以被替换成两个字符 b i c i b_{i}c_{i} bici每一种变化均可以无限次发生。 科学家们表示,如果一个DNA序列 s 3 s_3 s3在整个进化过程中可以最终变为 s 1 s_1 s1 s 2 s_2 s2 或许是经过了不同的步骤数,那么DNA序列分别为 s 1 s_1 s1 s 2 s_2 s2的两个生物就会有一个共同祖先。现在给出 s 1 s_1 s1 s 2 s_2 s2,你的任务是弄清楚分别拥有这两种DNA序列的生物是否有共同祖先。如果存在,你需要找出所有共同祖先中长度最短的DNA序列。

二.题解

这道题的数据范围为 0 ≤ n ≤ 50 0\leq n \leq 50 0n50所以必定很暴力,先考虑一个 O ( n 4 ) O(n^4) O(n4)的算法:

定 义 d p [ i ] [ j ] 表 示 s 1 的 前 i 个 字 母 与 s 2 的 前 j 个 字 母 缩 成 相 同 的 一 串 字 母 的 最 短 长 度 , 答 案 就 为 d p [ l e n 1 ] [ l e n 2 ] 定义dp[i][j]表示s_1的前i个字母与s_2的前j个字母缩成相同的一串字母的最短长度,答案就为dp[len1][len2] dp[i][j]s1is2jdp[len1][len2]

然后考虑转移: d p [ i ] [ j ] = m i n ( d p [ i ] [ j ] , d p [ i − k 1 ] [ j − k 2 ] + 1 ) ( k 1 , k 2 表 示 s 1 和 s 2 的 从 j 开 始 分 别 连 续 k 1 和 k 2 的 长 度 可 以 缩 成 同 一 字 母 ) dp[i][j] = min (dp[i][j], dp[i - k1][j - k2] + 1)(k1,k2表示s1和s2的从j开始分别连续k1和k2的长度可以缩成同一字母) dp[i][j]=min(dp[i][j],dp[ik1][jk2]+1)(k1k2s1s2jk1k2)
方程式就是这样,下面还有一个预处理,处理出 s 1 和 s 2 中 l 到 r 可 以 缩 成 的 字 母 ( 1 ≤ l ≤ l e n 1 , l e n 2 , 1 ≤ r ≤ l e n 1 , l e n 2 ) s_1和s_2中l到r可以缩成的字母(1\leq l \leq len1,len2,1\leq r \leq len1,len2) s1s2lr1llen1,len2,1rlen1,len2,这是一个时间复杂度为 O ( n 3 2 6 2 ) O(n^326^2) O(n3262)的预处理,也很暴力,我们把这个预处理存在一个数组 f [ i ] [ j ] [ k ] f[i][j][k] f[i][j][k]里。

f [ i ] [ j ] [ k ] 存 i 到 j 中 的 字 母 能 够 缩 成 的 某 个 字 母 , k = 0 则 是 s 1 , k = 1 则 是 s 2 f[i][j][k]存i到j中的字母能够缩成的某个字母,k=0则是s_1,k=1则是s_2 f[i][j][k]ijk=0s1k=1s2

我们要用二进制来存这些字母, 2 2 6 = 67108864 2^26=67108864 226=67108864,用int就行。
这道题就做完了。

三.Code

#include 
#include 
#include 
using namespace std;

#define M 55
#define INF 0x3f3f3f3f

int n, len1, len2, mp[M][M], f[M][M][5], dp[M][M];
char s1[M], s2[M];

void prepare (char s[], int len, int type){
    for (int i = 1; i <= len; i ++)
        f[i][i][type] |= 1 << (s[i] - 'a');
    for (int r = 1; r <= len; r ++){
        for (int l = r - 1; l >= 1; l --){
            for (int k = l; k < r; k ++){
                for (int i = 0; i < 26; i ++){
                    if (! (f[l][k][type] >> i & 1))
                        continue;
                    for (int j = 0; j < 26; j ++){
                        if (! (f[k + 1][r][type] >> j & 1))
                            continue;
                        f[l][r][type] |= mp[i][j];
                    }
                }
            }
        }
    }
}
int main (){
    scanf ("%s%s", s1 + 1, s2 + 1);
    len1 = strlen (s1 + 1);
    len2 = strlen (s2 + 1);
    scanf ("%d", &n);
    char tmp[10];
    for (int i = 1; i <= n; i ++){
        scanf ("%s", tmp);
        mp[tmp[3] - 'a'][tmp[4] - 'a'] |= 1 << (tmp[0] - 'a');
    }
    prepare (s1, len1, 0);
    prepare (s2, len2, 1);
    for (int i = 0; i <= len1; i ++)
        for (int j = 0; j <= len2; j ++)
            dp[i][j] = INF;
    dp[0][0] = 0;
    for (int i = 1; i <= len1; i ++){
        for (int j = 1; j <= len2; j ++){
            for (int k1 = 1; k1 <= i; k1 ++){
                for (int k2 = 1; k2 <= j; k2 ++){
                    if (! (f[k1][i][0] & f[k2][j][1]))
                        continue;
                    if (dp[k1 - 1][k2 - 1] == INF)
                        continue;
                    dp[i][j] = min (dp[i][j], dp[k1 - 1][k2 - 1] + 1);
                }
            }
        }
    }
    if (dp[len1][len2] == INF)
        printf ("-1\n");
    else
        printf ("%d\n", dp[len1][len2]);
    return 0;
}

谢谢!

你可能感兴趣的:(DP典例)