SDUT 1008最长公共子序列

2023-05-16

题目链接

https://acm.sdut.edu.cn/onlinejudge2/index.php/Home/Index/problemdetail/pid/1008.html

分析

题目类型:变维DP

状态定义

         对于动态规划而言,最难的地方就在于状态的定义,定义一个良好的状态可以极大的帮助我们解决问题,反之,如果定义一个糟糕的状态尽管这个状态是正确的,也很有可能会因为状态转移方程难以确定从而导致无法解决问题。对于这个题目,我们先从普通的LCS入手。

         这里假设读者都掌握了LCS的相关知识。在计算两个字符串的最长公共子序列是,显然我们可以定义转态为dp[i][j],用于表示第一个串的前i,第二个串的前j个字符的LCS。并有状态转移方程

if (s[i] == s[j]) dp[i][j] = max(dp[i - 1][j - 1] + 1, max(dp[i][j - 1], dp[i - 1][j]));
else dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);

         同样的道理我们可以定义三个字符串的状态为dp[i][j][k],n个字符串的状态为dp[x1][x2]```[xn]。不难发现 空间复杂度是O(M^N),M表示字符的大小,N表示字符串的个数,所以单从空间复杂度上考虑这种方式显然不可取。其次,即便是空间没有任何限制,我们也会发现,由于状态转移方程与N相关,所以根本就没有办法进行状态转移(如果OJ对于代码长度没有任何限制,对空间没有任何限制,并且最重要的是你的耐心足够的好,你可以试着写一下)

         分析N个串的状态转移方程,不难注意到我们按照普通的方式进行实现需要使用n重for循环,而for循环遍历对于各个位置x1, x2```xn会产生一个固定的次序,以二重for循环为例

void test() {
    int cnt = 0;
    for (int i = 0; i < 3; ++i) {
        for (int j = 0; j < 2; ++j) {
            printf("%d\n", cnt++);
        }
    }
}

         执行此函数,我们可以看到程序依次输出了0 、1、2、3、4、5

          对于不同的二元组(i, j),cnt的值各不相同,因此我们可以借鉴这种思想,定义一个从n元组(x1, x2, ```, xn)到一个整数的哈希函数,从而将状态映射到一个一维数组中去

          同样for循环程序为例,我们可以发现,对于二元组(i,j),cnt的值为 i * j + j,对于三元组(i, j, k)我们可以发现cnt = i * j * k + j * k + k。因此我们可以大胆的猜测对于n元组(x1, x2, ```, xn)cnt = x1 * x2 *``` * xn + x2 * x3 *```*xn +``` + xn,所以我们可以递归的计算这个唯一的值cnt,哈希函数递归实现如下

int id(int deep, int idx) {
    if (deep < 0) return 0;
    return id(deep - 1, idx * len[deep]) + idx * _end[deep];
}

         通过上面的散列函数我们可以把状态用一个一维数组保存,即实现记忆化

         经过上面的努力,我们成功的定义了状态。这个状态定义的极妙,通过这个状态我们可以比较轻松的进行状态转移

状态转移

      对于LCS问题,我们易知,如果两个串当前位置的字符相同,我们有状态转移方程

dp[i][j] = max(dp[i - 1][j - 1] + 1, max(dp[i][j - 1], dp[i - 1][j]));

     同样的道理,对于n个字符串的LCS问题,当当前位置的字符全部相同是,我们可以让位置向前移动,最后的结果是ans = max(ans, dp() + 1),dp()代表子问题最优解,ans代表当前问题最优解;对于不相等的问题,普通的LCS是向后移动,也就是暴力枚举,同样的道理,对于n个字符串的LCS 问题,我们同样的进行暴力枚举

使用数据结构

为了实现散列函数我们必须知道两个东西,一是每个字符串的长度,二是当前下标位置,在我的程序中,分别用len和_end表示

为了实现记忆化,我们需要开一个一维数组dp

为了实现比较,我们必须把字符串的信息保存下来,因此我们需要定义二维的字符数组

以上即是程序中涉及的主要的数据结构

参考代码

#include <stdio.h>
#include <string.h>
#include <algorithm>

using namespace std;

#define N 110
#define M 33000

int n;
int dp[M];
char s[N][M];
int len[N], _end[M];

//通过一个散列函数将状态映射到一个一维数组之中
int id(int deep, int idx) {
    if (deep < 0) return 0;
    return id(deep - 1, idx * len[deep]) + idx * _end[deep];
}

int dfs() {
    int i;   //作用域为整个函数,方便以后的使用
    int idx = id(n - 1, 1);
    if (dp[idx] == -1) {  //该子问题没有处理过
        //分为匹配和不匹配两种情况
        dp[idx] = 0;
        for (i = 0; i < n - 1; ++i)
            if (s[i][_end[i]] != s[i + 1][_end[i + 1]]) break;
        if (i >= n - 1) {   //匹配时
            for (i = 0; i < n; ++i) {
                if (_end[i]) --_end[i]; else break;
            }
            //状态转移
            if (i < n) dp[idx] = max(dp[idx], 1);
            else dp[idx] = max(dp[idx], dfs() + 1);
            while ((--i) >= 0) _end[i]++; //恢复初始状态
        } else {  //不匹配时
            for (i = 0; i < n; ++i) {
                if (_end[i]) {
                    _end[i]--;
                    dp[idx] = max(dp[idx], dfs());
                    _end[i]++;
                }
            }
        }
    }
    return dp[idx];   //返回最优解
}

int main() {
    int T;
    scanf("%d", &T);
    while (T--) {
        scanf("%d", &n);
        for (int i = 0; i < n; ++i) {
            scanf("%s", s[i]);
            len[i] = strlen(s[i]); _end[i] = len[i] - 1;
        }
        memset(dp, -1, sizeof(dp));
        printf("%d\n", dfs());
    }
    return 0;
}

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

SDUT 1008最长公共子序列 的相关文章

  • 二叉树、树、森林之间的转化

    树转二叉树 将孩子作为左孩子 xff0c 将第一个兄弟作为右孩子 二叉树转树 将左孩子的右孩子作为自己的孩子 森林转二叉树 与转树差不多 xff0c 唯一需要注意的是要把第二棵树的根节点作为第一棵树的一个兄弟 二叉树转森林 把右孩子断开成一
  • 最小堆练习代码

    include lt stdio h gt include lt stdlib h gt define INF 100000 define MinData 10 typedef int Element typedef struct Node
  • 不带头结点的单链表逆置操作

    reverse函数负责逆置工作 include lt stdio h gt include lt stdlib h gt typedef struct Node int data struct Node next Node List voi
  • 我的第一个Python程序

    突然想学Python了 xff0c 今天开始学习 xff0c 编出了我的第一个Python程序 calculate the area and circumference of a circle from its radius import
  • PowerShell-压缩解压缩文件

    PowerShell 压缩解压缩文件 缘起压缩文件1 调用第三方工具自带命令2 PowerShell命令压缩 解压缩文件1 PS命令解压2 Windows内置解压3 调用COM对象 附 xff1a 查看PowerShell版本方法 缘起 前
  • Python绘制五角星

    借用了turtle这一模块的帮助 import turtle turtle forward 100 turtle right 144 turtle forward 100 turtle right 144 turtle forward 10
  • 小明的调查作业(南阳理工OJ48)

    描述 小明的老师布置了一份调查作业 xff0c 小明想在学校中请一些同学一起做一项问卷调查 xff0c 聪明的小明为了实验的客观性 xff0c 想利用自己的计算机知识帮助自己 他先用计算机生成了N个1到1000之间的随机整数 xff08 0
  • 如何加快cin\cout读取数据的速度

    在使用cin cout前加上 ios sync with stdio false 这可以加快读取数据的速度 xff0c 但是有一个非常不好的副作用就是不能与scanf这类的输入输出方法混用了 xff0c 我就是因为混用结果有一个题提交了10
  • Python读写文件

    coding utf 8 向指定文件中存储指定内容 def text create name msg full path 61 name 43 39 txt 39 file 61 open full path 39 w 39 file wr

随机推荐

  • eclipse设置编码格式

    打开文件 xff0c 点击edit下的setCoding 如图 弹出一个对话框 xff0c 点击other 选择utf 8 先点击apply再点击OK 完成
  • Python打印九九乘法表

    打印九九乘法表 def create table for row in range 1 10 for column in range 1 row 43 1 print str row 43 39 39 43 str column 43 39
  • UVA10562解题报告

    我的GitHub地址 xff1a https github com DongChengrong ACM Program 仔细观察他给出的树 xff0c 我们可以发现这棵多叉树长得比较有特点 最上是树根 xff0c 而每一个节点只要有孩子下面
  • 实用函数之判断素数

    功能 xff1a 判断一个数是否是素数 素数概念 xff1a 只能被1和它本身整除的数 实现语言 xff1a C C 43 43 int is prime int n if n lt 61 1 return 0 int m 61 floor
  • 打不开IDLE

    我开始安装的是Python3 6 xff0c 后来发现很多库不支持3 6只支持2 7 xff0c 所以我又重装了2 7 xff0c 这时再打开IDLE就打不开了 经过我的几次努力终于找到了解决方案 将C盘 用户 xff08 user xff
  • Ubuntu系统中使用gdebi安装.deb文件

    传统软件套件管理工具 dpkg 定义 xff1a dpkg为 Debian 专门开发的套件管理系统 xff0c 方便软件的安装 更新及移除 首先 xff0c 要安装dpkg软件管理工具 suo span class token functi
  • IDLE设置字体大小

    点击option gt Configure 在出现的弹框里修改字体大小 xff0c 注意字体不能是楷体等与中文相关的字体 xff0c 否则点击OK无效 xff08 虽然在我电脑上确实是这样但是不知道为什么 xff0c 如果有知道的同学希望能
  • 关于递归问题的一点见解

    背景 前不久参加牛客网的有书共读活动意外的获得了一本 具体数学 xff0c 对此感谢牛客 其次领书是要条件的 xff0c 条件就是写读书笔记 搞ACM的都知道具体数学是本什么样的神书 xff08 也可能是我太弱了 xff09 xff0c 不
  • Python乱码问题

    原因 xff1a 源文件编码格式是UTF 8而Windows的编码格式是GBK xff0c 所以出现了乱码现象 解决方案一 xff1a 编码格式改成GBK就可以了 如下 第二种解决方案 coding utf 8 import sys typ
  • Python创建模块并导入

    Python创建自己的模块很方便 xff0c 所有的 py文件都被视为是一个模块 我们可以用import 文件名的方式把它导入自己的新文件 不过我们要注意创建的模块要符合命名规范 xff0c 比如首字母不能是数字等 如果首字母是数字就会出现
  • Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK

    现象 xff1a android util AndroidRuntimeException Calling startActivity from outside of an Activity context requires the FLA
  • Python查看库安装路径

    打开IDLE xff0c 输入如下两行指令 import sys print sys path 3 如下图
  • 安装pip教程

    相信在安装pip之前大家一定都安装了Python 所以我们找到Python的根目录下的Script文件夹 xff08 如果忘记了我们的安装目录可以参考文章http blog csdn net dongchengrong article de
  • pip常用指令

    安装库 xff1a pip install packageName flask 卸载库 xff1a pip uninstall packageName flask 升级库 xff1a pip install upgrade packageN
  • Python操作文件和目录

    对文件和目录进行操作是在我们开发过程中必不可少的一环 xff0c 下面是我整理的一些常用的对文件和目录进行操作的语句 xff0c 希望能帮到你 首先是导包 xff0c 导入包os xff0c import os 1 获取当前Python脚本
  • UVA227解题报告

    因为网格中存在空格所以用gets录入 xff0c 首先录入一行数据 xff0c 如果第一个字符为 39 Z 39 则break退出循环 其次是对指令的接受与处理 接受指令可以用getchar xff0c 遇到换行符跳过 处理也很简单 xff
  • win10/win11系统 如何将7-zip设置为默认软件?

    步骤 第一步 xff0c 首先下载7 zip第二步 xff0c 点击下载的7 zip安装包第三步 xff0c 进入你安装7 zip的文件夹下 xff0c 然后找到7 Zip File Manager第四步 xff0c 右键以管理员权限运行7
  • 南阳理工OJ915解题报告

    描述 Shiva得到了两个只有加号和减号的字符串 xff0c 字串长度相同 Shiva一次可以把一个加号和它相邻的减号交换 他想知道最少需要多少次操作才能把第一个字符串变换成第二个字符串 你现在要去帮助他完成那个这个问题 输入 多组测试数据
  • Color the fence

    Color the fence 时间限制 xff1a 1000 ms 内存限制 xff1a 65535 KB 难度 xff1a 2 描述 Tom has fallen in love with Mary Now Tom wants to s
  • SDUT 1008最长公共子序列

    题目链接 https acm sdut edu cn onlinejudge2 index php Home Index problemdetail pid 1008 html 分析 题目类型 xff1a 变维DP 状态定义 对于动态规划而