基础算法

假设我们有两个字符串:,每个字符串由A C G T四个字母组成。

在两个字符串上,都有三种可能的编辑操作(突变):

  1. 删除某个字符
  2. 在某个位置插入字符
  3. 改变某个字符

每一个编辑操作都有惩罚值。用D(R,B)表示字符串R和B的最小编辑距离(总惩罚值)。

在这里,我们将三种编辑操作的惩罚值都设为1。

定义矩阵D,X维和Y维分别表示字符串B和R。在矩阵的上,我们尝试对两个字符串对应位置的字符进行匹配,并计算相应位置的分数。

在矩阵上DP的过程实际上具有生物学意义。比如,上下或者左右移动意味着插入或者删除,在对角线上移动意味着匹配/失配。

很明显,在对角线上移动比左右/上下移动更容易找到符合条件的匹配。

示例图片:

伪代码:

算法优化

上面的算法的时间复杂度为O(m^2)。

在上一个算法的推导过程中,我们可以发现:在矩阵上DP的过程中,偏离中心对角线很远的元素基本不会符合答案要求。

设k为最大的惩罚值,通过限制搜索过程中D中元素的大小,只要大于k就放弃该元素,就可以达到优化时间复杂度到O(km)的效果。

在此基础上,考虑能否通过优化DP矩阵的两个维度,通过直接限制搜索空间的方法将空间复杂度也优化到O(km)。

可以发现,一个比较好的匹配总是沿对角线进行的。而且,如果遍历每一条对角线,一定能找到最终匹配。

定义差异值e(e<=k)作为新矩阵的一个维度。考虑到算法基础是以对角线为中心,向两边拓展差异值<=k,那么对角线编号应当作为矩阵的另一个维度。

我们将计算的矩阵重新定义为,其中d代表第几个对角线,e代表当前差异值。

可以发现,目前缺少一个代表"位于对角线的哪个位置"的元素。那么,就将元素值设为当前位于模式串的第几个字符,也就是之前D矩阵的行数即可。

现在,L矩阵上元素的值指对于第d个对角线,在差异值为e的情况下,最多能拓展到第几行。

接下来,考虑如何计算这个矩阵。

思考对角线之间的关系,可以发现:对于任意一条对角线,可以通过其相邻两条对角线当前拓展到的最大行数+1更新这一条对角线能拓展到的最大行数。这是因为通过一个插入/删除操作可以使两条相邻对角线上的对齐转化到当前对角线上。

因此,对于,只有

  • 位于同一条对角线且差异值比他小1的元素+1
  • 在上面的一条对角线上, +1
  • 在下面的一条对角线上,+1

可以更新它的值。

伪代码

C++代码

#include<cstdio>
#include<iostream>
#include<cmath>
#include<cstring>
#include<map>
const int INF = 20000000;
const int MAXN = 2000;
const int MAXK = 100; void setValue(int L[MAXN][MAXK], int i, int j, int value) {
L[i + (MAXN >> 1)][j + (MAXK >> 1)] = value;
} int getValue(int L[MAXN][MAXK], int i, int j) {
return L[i + (MAXN >> 1)][j + (MAXK >> 1)];
} bool sameChar(char r[], char b[], int rIndex, int bIndex,int lenB,int lenR) {
rIndex--;
bIndex--;
if (rIndex == 0 && bIndex == 0)return 1;
if (rIndex < 0 || bIndex < 0) {
printf("WRONG!");
return 0;
}
if (rIndex >= lenR || bIndex >= lenB)return 0;
return r[rIndex] == b[bIndex];
} bool isSame(char r[], char b[],int lenB,int lenR) {
if (lenR != lenB)return 0;
for (int i = 0; i < lenR; i++) {
if (r[i] != b[i])return 0;
}
return 1;
} void printAll(int L[MAXN][MAXK], int k) {
// 第几个对角线
for (int i = -k; i <= k; i++) {
// 差异值
for (int j = -k; j <= k; j++) {
printf("L[%d][%d]=%d ", i, j, getValue(L, i, j));
}
printf("\n");
}
} void init(int L[MAXN][MAXK], char r[], char b[], int k) {
for (int d = -(k + 1); d <= k + 1; d++) {
setValue(L, d, abs(d) - 2, -INF);
if (d < 0) {
setValue(L, d, abs(d) - 1, abs(d) - 1);
} else {
setValue(L, d, abs(d) - 1, -1);
}
}
} #define scanf_s scanf int max(int a,int b){
if(a>b)return a;
return b;
} int main() {
// 两个字符串
char r[MAXN], b[MAXN]; int k; int dp[MAXN][MAXK];
memset(dp, 0, sizeof(dp)); printf("R Input:");
scanf_s("%s", &r);
printf("B Input:");
scanf_s("%s", &b);
printf("k:");
scanf_s("%d", &k);
init(dp, r, b, k); int lenB=strlen(b);
int lenR=strlen(r); if (k == 0) {
if (isSame(r, b,lenB,lenR)) {
printf("yes\n");
} else {
printf("no\n");
}
return 0;
} // 遍历每一个差异值
for (int e = 0; e <= k; e++) {
// 遍历每一个差异值的所有对角线
for (int d = -e; d <= e; d++) {
int row = max(getValue(dp, d, e-1) + 1,
max(getValue(dp, d - 1, e - 1), getValue(dp, d + 1, e - 1) + 1));
while (sameChar(r, b, row + 1, row + 1 + d,lenB,lenR)) {
row++;
}
setValue(dp, d, e, row);
if (getValue(dp, d, e) == lenR) {
printf("yes\n");
//printAll(dp, k);
return 0;
}
}
} //printAll(dp, k);
printf("no\n");
return 0;
}

对拍

数据生成

#include<cstdio>
#include<iostream>
#include<cmath>
#include<string>
#include<algorithm>
#include<ctime>
#include<map>
const int INF = 20000000;
const int MAXN = 2000;
using namespace std; int min(int a,int b){
if(a<b)return a;
return b;
} string randomChar(){
int opt=rand()%4;
switch(opt){
case 0:
return "A";
case 1:
return "T";
case 2:
return "C";
case 3:
return "G";
}
} string createSequence(string str,int len){
for(int i=0;i<len;i++){
str+=randomChar();
}
return str;
} string insert(string str){
int len=str.length();
int index=rand()%len;
return str.insert(index,randomChar());
} string replace(string str){
int len=str.length();
int index=rand()%len;
char oldChar=str[index];
while(oldChar==str[index]){
str=str.replace(index,1,randomChar());
}
return str;
} string _delete(string str){
int len=str.length();
int index=rand()%len;
return str.erase(index,1);
} string randomOpt(string origin,int optNum){
string tmp=origin;
for(int i=1;i<=optNum;i++){
int opt=rand()%3;
switch(opt){
case 0:
tmp=insert(tmp);
continue;
case 1:
tmp=replace(tmp);
continue;
case 2:
tmp=_delete(tmp);
continue;
}
}
return tmp;
} int main() {
srand((int)time(NULL));
int len=rand()%10+5;
int optNum=rand()%(min(40,len-3));
string b="",r="";
b=createSequence(b,len);
r=randomOpt(b,optNum); int is=rand()%2;
int delta=rand()%3;
if(is==0){
delta*=-1;
} if(r.length()>b.length()){
printf("%s\n",b.c_str());
printf("%s\n",r.c_str());
printf("%d\n",optNum+delta);
}else{
printf("%s\n",r.c_str());
printf("%s\n",b.c_str());
printf("%d\n",optNum+delta);
} return 0;
}

正确代码

#include<cstdio>
#include<iostream>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<map>
const int INF = 20000000;
const int MAXN = 2000; void printAll(int dp[MAXN][MAXN],int lenR,int lenB) {
for (int i = 0; i <= lenR; i++) {
for (int j = 0; j <= lenB; j++) {
printf("%d ", dp[i][j]);
}
printf("\n");
}
} int min(int a,int b){
if(a<b)return a;
return b;
} int dp[MAXN][MAXN];
int main() {
// 涓や釜瀛楃涓?
char r[MAXN], b[MAXN];
int k; printf("R Input:");
scanf("%s", &r);
printf("B Input:");
scanf("%s", &b);
printf("k:");
scanf("%d", &k); int lenR = strlen(r), lenB = strlen(b);
dp[0][0] = 0;
for (int j = 1; j <= lenB; j++) {
dp[0][j] = j;
}
for (int i = 1; i <= lenR; i++) {
dp[i][0] = i;
}
for (int i = 1; i <= lenR; i++) {
for (int j = 1; j <= lenB; j++) {
int tmp = dp[i - 1][j - 1];
if (r[i - 1] != b[j - 1])tmp++;
dp[i][j] = min(dp[i - 1][j] + 1, min(dp[i][j - 1] + 1, tmp));
}
}
int minn=INF;
for (int i = 1; i <= lenB; i++) {
minn = min(minn, dp[lenR][i]);
}
// printAll(dp, lenR, lenB);
if (minn <= k) {
printf("yes\n");
}else {
printf("no\n");
}
return 0;
}

Landau-Vishkin的更多相关文章

  1. 2019QM大作业2-weyl半金属Landau Level

    目录 说明 for cnblog QM大作业2--weyl半金属的Landau Level \(\boldsymbol{Abstract}\) 说明 Landau Level 自旋与pauli mat ...

  2. [笔记] 兰道定理 Landau's Theorem

    兰道定理的内容: 一个竞赛图强连通的充要条件是:把它的所有顶点按照入度d从小到大排序,对于任意\(k\in [0,n-1]\)都不满足\(\sum_{i=0}^k d_i=\binom{k+1}{2} ...

  3. python编码最佳实践之总结

    相信用python的同学不少,本人也一直对python情有独钟,毫无疑问python作为一门解释性动态语言没有那些编译型语言高效,但是python简洁.易读以及可扩展性等特性使得它大受青睐. 工作中很 ...

  4. 2016 ACM/ICPC Asia Regional Dalian Online(更新到五道题)

    1006 Football Games 这道题输入也很阴险!!! 这道题过题姿势最优雅的,不是if else if else if.那样很容易wa的. 如果没有平手选项, 赢得加一分的话, 可以用La ...

  5. 一些对数学领域及数学研究的个人看法(转载自博士论坛wcboy)

    转自:http://www.math.org.cn/forum.php?mod=viewthread&tid=14819&extra=&page=1 原作者: wcboy 现在 ...

  6. 2016 ACM/ICPC Asia Regional Dalian Online 1006 /HDU 5873

    Football Games Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)To ...

  7. 单点登录技术:微软Passport单点登录协议和自由联盟规范

    随着互联网络应用的普及,越来越多的人开始使用互联网上提供的服务.然而目前提供服务的网站大多采用用户名.口令的方式来识别用户身份,这使得用户需要经常性的输入自己的用户名.口令.显然这种认证方式存在着弊端 ...

  8. GNU scientific library

    GNU scientific library 是一个强大的C,C++数学库.它涉及的面很广,并且代码效率高,接口丰富.正好最近做的一个项目中用到多元高斯分布,就找到了这个库. GNU scientif ...

  9. 2016大连网络赛 Football Games

    Football Games Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others) P ...

  10. 浅谈时间复杂度- 算法衡量标准Big O

    写在前面: 今天有一场考试,考到了Big-O的知识点,考到了一道原题,原题的答案我记住了,但实际题目有一些改动导致答案有所改动,为此作者决定重新整理一下复杂度相关知识点 Efficiency and ...

随机推荐

  1. POJ3903Stock Exchange (LIS)

    学了下BIT,炸了... #include <iostream> #include <cstdio> #include <cstring> #include < ...

  2. Docker 13 Dockerfile

    参考源 https://www.bilibili.com/video/BV1og4y1q7M4?spm_id_from=333.999.0.0 https://www.bilibili.com/vid ...

  3. java-RandomAccessFile操作以及IO流简单使用

    1.1RandomAccessFile--使用RAF读写基本类型数据,以及了解Raf的指针操作 write有相对应的写入基本类型的方法 void seek(Long pos)调整RAF指针位置,可以在 ...

  4. 算法模板:spfa

    #include<iostream> #include<algorithm> #include<cstring> #include<string> #i ...

  5. OID天下第一 (双指针,LCT,线段树)

    题面 或曰:"笑长天下第一!",OID 喜得合不拢嘴:"哈哈哈哈哈哈--" OneInDark 是天下第一的. OneInDark 给了你一个 n n n 个点 ...

  6. C# using()的本质

    " 程序世界没有秘密,所有答案都在源码里 " 01.点明观点 C#中,非托管资源使用之后必须释放,而using()是使用非托管资源的最佳方式,可以确保资源在代码块结束之后被正确释放 ...

  7. format添加未知个参数方法

    一个python巧妙技巧,分享给大家 我的需求是将一个dict的键都format输出,用到了*对字典解包 data = {a: 1, b: 2...} msg = f"{'{} '*len( ...

  8. 我的Vue之旅、02 ES6基础、模块、路径、IO

    自定义模块 为什么要模块?模块化源代码能给我们带来什么好处? 试想一个巨无霸网购平台,在没有模块化的情况下,如果出现bug,程序员就要在几百万行代码里调试,导致后期维护成本上升,为了解决问题,模块化按 ...

  9. 我的Go并发之旅、01 并发哲学与并发原语

    注:本文所有函数名为中文名,并不符合代码规范,仅供读者理解参考. 上下文 上下文(Context)代表了程序(也可以是进程,操作系统,机器)运行时的环境和状态,联系程序整个生命周期与资源调用,是程序可 ...

  10. Fluentd 使用 multiline 解析器来处理多行日志

    转载自:https://mp.weixin.qq.com/s?__biz=MzU4MjQ0MTU4Ng==&mid=2247500439&idx=1&sn=45e9e0e0ef ...