AcWing
  • 首页
  • 课程
  • 题库
  • 更多
    • 竞赛
    • 题解
    • 分享
    • 问答
    • 应用
    • 校园
  • 关闭
    历史记录
    清除记录
    猜你想搜
    AcWing热点
  • App
  • 登录/注册

字符串处理——动态规划

作者: 作者的头像   wisdom-77 ,  2020-07-26 23:54:44 ,  所有人可见 ,  阅读 1012


3


2

题目来源于《挑战程序设计竞赛》,题目涉及的算法只对应相关章节 (不一定是最优解的,思路写的有点简陋)


例题1 编辑距离 传送门

题意:
编辑距离,是指两个字串之间,由一个转成另一个所需的最少编辑操作次数。许可的编辑操作包括将一个字符替换成另一个字符,插入一个字符,删除一个字符。

思路:
dp[i][j]:所有将a中的前i个字母变成 b中前j个字母的集合的操作次数
对a中的第i个字母操作,分为以下四种操作
1. 在该字母之后添加: dp[i][j] = dp[i][j-1] + 1
2. 删除该字母: dp[i][j] = dp[i-1][j] + 1
3. 替换该字母: dp[i][j] = dp[i-1][j-1] + 1
4. 啥也不做(对应结尾字母相同): dp[i][j] = dp[i-1][j-1]

代码:

#include <bits/stdc++.h>
using namespace std;

const int N = 1e3 + 10;
char s1[N], s2[N];
int dp[N][N];

int edit_distance(char s1[], char s2[], int n, int m)
{
    for (int i = 1; i <= n; i++) dp[i][0] = i;
    for (int i = 1; i <= m; i++) dp[0][i] = i;
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++)
        {
            if (s1[i] == s2[j]) dp[i][j] = dp[i - 1][j - 1];
            else dp[i][j] = min(min(dp[i - 1][j], dp[i][j - 1]), dp[i - 1][j - 1]) + 1;
        }
    return dp[n][m];
}

int main()
{
    cin >> s1 + 1;
    cin >> s2 + 1;
    int n = strlen(s1 + 1);
    int m = strlen(s2 + 1);

    cout << edit_distance(s1, s2, n, m);
}

例题2 禁止字符串

题意:
考虑只由’A’,’G’,’C’,’T’四种字符组成的DNA字符串。给定一个长度为K的字符串S。请计算长度恰好为n 且不包含 S的宇符串的个数。输出个数 mod 10009 后的结果。

思路:
trans[i][j]:在S[1…i]添加字符cho[j]后(新串T), 满足 T的后缀 和 S的前缀 相等的最大长度
dp[i][j]:当前构造字符串P长度为i,满足𝑃[𝑖 −𝑗+1…𝑖]=𝑆[1…𝑖]的方案数

可得到如下转移方程:
设 ne = trans[i][j], 若 ne 合法(即 ne != k, k = |S|) dp[i+1][ne] += dp[i][j]

代码:

//禁止字符串 
#include <bits/stdc++.h>

using namespace std;

const int K = 110;
const int N = 10010;
const int C = 5;
const int mod = 10009;

int n, k;
string cho = "AGCT", s;

//trans[i][j]:当前已经匹配i个字符,添加字符cho[j]后,匹配的字符个数
int trans[K][C];
//dp[i][j]:当当前字符串长度为i,状态j结尾的方案数
int dp[N][K];

int main()
{
    cin >> n >> k;
    cin >> s;

    //预处理trans
    memset(trans, 0, sizeof trans);
    for (int i = 0; i < k; i++)
    {
        string tmp = s.substr(0, i);
        for (int j = 0; j < 4; j++)
        {
            string cur = tmp + cho[j];
            while (s.substr(0, cur.length()) != cur)
                cur = cur.substr(1);
            trans[i][j] = cur.length();
        }
    }

    //计算dp
    memset(dp, 0, sizeof dp);
    dp[0][0] = 1;
    for (int i = 0; i < n; i++)
        for (int j = 0; j < k; j++)
            for (int c = 0; c < 4; c++)
            {
                int ne = trans[j][c];
                if (ne == k) continue;
                dp[i + 1][ne] = (dp[i + 1][ne] + dp[i][j]) % mod;
            }

    //计算总方案数
    int tot = 0;
    for (int i = 0; i < k; i++) 
        tot = (tot + dp[n][i]) % mod;
    cout << tot << endl;

    return 0;
}

例题3 DNA repair 传送门

题意:
考虑只由’A’,’G’,’C’,’T’四种字符组成的DNA字符串。给定一个原字符串s,和n个禁止模式字符串P1,P2…Pn。请修改宇符串s,使得其中不包含任何禁止模式。每次修改操作只能将S中的某个字符修改为其他字符。如果不存在这样的修改,请输出-1,否则,输出所需的最少修改回数。

思路:
考虑从左向右修改,以修改到的位置和对应的后缀状态进行动态规划

后缀状态表示方法:
先求出𝑃[1]…𝑃[𝑁]所有字符串的前缀串
以样例2为例,前缀串Q[1…K]为{“A”, “"T” ,”𝑇𝐺”}
为了避免重复,去重,每个前缀串的下标作为对应的后缀状态
对每个Q[i],判断Q[i]的后缀中是否含有禁止串

状态转移数组trans[i][j] :
与例题2的做法类似,添加字符后,反复删除第一个字符, 直到等于某个状态的字符串,即为转移到的状态

代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>

using namespace std;

typedef long long ll;

const int N = 55;
const int S = 1010;

int n, cas;
string s, p[N], cho = " AGCT";
bool flag[S];
int trans[S][5], dp[S][S];

int main()
{
    ios::sync_with_stdio(false);

    while (cin >> n && n)
    {
        for (int i = 1; i <= n; i++)
        {
            cin >> p[i];
            p[i] = " " + p[i];
        }
        cin >> s; s = " " + s;

        vector<string>pre;
        pre.push_back(" ");
        for (int i = 1; i <= n; i++)
            for (int j = 1; j < p[i].length(); j++)
            pre.push_back(" " + p[i].substr(1, j));
        stable_sort(pre.begin(), pre.end());
        pre.erase(unique(pre.begin(), pre.end()), pre.end());
        int m = (int)pre.size() - 1;

        for (int i = 0; i <= m; i++) flag[i] = 1;
        for (int i = 1; i <= m; i++)
            for (int j = 1; j <= n; j++)
            {
                int lens = pre[i].length(), lent = p[j].length();
                if (lens >= lent && pre[i].substr(lens - lent + 1) == p[j].substr(1))
                {
                    flag[i] = 0;
                    break;
                }
            }

        for (int i = 0; i <= m; i++)
            for (int j = 1; j <= 4; j++)
            {
                string ne = pre[i] + cho[j];
                while (true)
                {
                    int pos = lower_bound(pre.begin(), pre.end(), ne) - pre.begin();
                    if (pos <= m && pre[pos] == ne)
                    {
                        trans[i][j] = pos;
                        break;
                    }
                    ne = " " + ne.substr(2);
                }
            }

        int lens = (int)s.length() - 1;
        for (int i = 0; i <= lens; i++)
            for (int j = 0; j <= m; j++)
            dp[i][j] = 0x3f3f3f3f;
        dp[0][0] = 0;
        for (int i = 0; i < lens; i++)
        {
            for (int j = 0; j <= m; j++)
            {
                if (dp[i][j] == 0x3f3f3f3f) continue;
                for (int k = 1; k <= 4; k++)
                {
                   int st = trans[j][k];
                   if (flag[st] == 0) continue;

                   if (s[i + 1] == cho[k]) dp[i + 1][st] = min(dp[i + 1][st], dp[i][j]);
                   else dp[i + 1][st] = min(dp[i + 1][st], dp[i][j] + 1);
                }
            }
        }

        int mi = 0x3f3f3f3f;
        for (int i = 0; i <= m; i++) mi = min(mi, dp[lens][i]);
        if (mi == 0x3f3f3f3f) mi = -1;
        printf("Case %d: %d\n", ++cas, mi);
    }

    return 0;
}

习题1 Easy Problem 传送门

题意:
给你一个字符串s,每个位置的字符都有一个删除的代价。现在可以删除一定量的字符使得新字符串不包含子序列”hard”(可以不连续),求最小代价。

思路:
dp[i][j]:当前处理位置为i, 包含t.subtr(1, i)子序列的最大权值和

代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>

using namespace std;

typedef long long ll;

const int N = 1e5 + 10;

int n, fare[N];
char s[N], t[10] = " hard";
//dp[i][j]:当前处理位置为i, 包含t.subtr(1, i)子序列的最大权值和
ll dp[N][5];

int main()
{
    scanf("%d", &n);
    scanf("%s", s + 1);
    for (int i = 1; i <= n; i++) scanf("%d", &fare[i]);

    ll sum = 0;
    for (int i = 1; i <= n; i++) sum += fare[i];

    for (int i = 1; i <= n; i++)
    {
        if (s[i] == 'h')
        {
            dp[i][0] = dp[i - 1][0];
            dp[i][1] = max(dp[i - 1][1], dp[i - 1][0]) + fare[i];
            dp[i][2] = dp[i - 1][2] + fare[i];
            dp[i][3] = dp[i - 1][3] + fare[i];
        }
        else if (s[i] == 'a')
        {
            dp[i][0] = dp[i - 1][0] + fare[i];
            dp[i][1] = dp[i - 1][1];
            dp[i][2] = max(dp[i - 1][1], dp[i - 1][2]) + fare[i];
            dp[i][3] = dp[i - 1][3] + fare[i];
        }
        else if (s[i] == 'r')
        {
            dp[i][0] = dp[i - 1][0] + fare[i];
            dp[i][1] = dp[i - 1][1] + fare[i];
            dp[i][2] = dp[i - 1][2];
            dp[i][3] = max(dp[i - 1][2], dp[i - 1][3]) + fare[i];
        }
        else if (s[i] == 'd')
        {
            dp[i][0] = dp[i - 1][0] + fare[i];
            dp[i][1] = dp[i - 1][1] + fare[i];
            dp[i][2] = dp[i - 1][2] + fare[i];
            dp[i][3] = dp[i - 1][3];
        }
        else
        {
            dp[i][0] = dp[i - 1][0] + fare[i];
            dp[i][1] = dp[i - 1][1] + fare[i];
            dp[i][2] = dp[i - 1][2] + fare[i];
            dp[i][3] = dp[i - 1][3] + fare[i];
        }
    }

    ll ma = 0;
    for (int i = 0; i < 4; i++) ma = max(ma, dp[n][i]);
    printf("%lld\n", sum - ma);

    return 0;
}

附
本人不太会写博客,若有错误,敬请指出,有点丑见谅

1 评论


用户头像
Draper   2020-07-27 00:40         踩      回复

这本书怎么样?


App 内打开
你确定删除吗?
1024
x

© 2018-2025 AcWing 版权所有  |  京ICP备2021015969号-2
用户协议  |  隐私政策  |  常见问题  |  联系我们
AcWing
请输入登录信息
更多登录方式: 微信图标 qq图标 qq图标
请输入绑定的邮箱地址
请输入注册信息