LCS-最长公共子序列的免去边界初始化的一些模板(记忆化搜索+记录路径的DP+优化空间的DP)
我们知道,输入一个字符串的时候一般是以0为下标的,而在最长公共子序列的计算中,任意字符串对空字符串的最长公共子序列都是0(对面啥都没有还有什么“公共”可言)。但是一般的输入下,0号位置是存储位置,这就因为无法表示而无法解决零长度这个问题,从而需要对结果矩阵进行边界初始化,也就是说把dp[0][i],dp[j][0]均初始化为0(i,j∈length(StringA),length(StringB))
有一个办法是:由于scanf("%s",location)后边的location指的是字符数组的地址,由于我们一般都是直接把数组名输上去,所以都是从首地址(0号位置)开始储存字符串,如果想从1开始计数,直接用scanf(“%s”,location+1)即可,这样就免去了上面的边界初始化。
下面的模板都是根据以上技巧所设(均在NYOJ(南阳理工学院)上测试过):
记忆化搜索模板(倒搜版):
#include <iostream>
#include <cstdio>
#include <bits/stdc++.h>
#include <map>
#include <algorithm>
#include <stack>
#include <iomanip>
#include <cstring>
#include <cmath>
#define DETERMINATION main
#define lldin(a) scanf("%lld",&a)
#define din(a) scanf("%d",&a)
#define printlnlld(a) printf("%lld\n",a)
#define printlnd(a) printf("%d\n",a)
#define printlld(a) printf("%lld",a)
#define printd(a) printf("%d",a)
#define reset(a,b) memset(a,b,sizeof(a))
const int INF=0x3f3f3f3f;
using namespace std;
const double PI=acos(-1);
typedef long long ll;
typedef unsigned long long ull;
typedef long double ld;
const int mod=1000000007;
const int tool_const=1999112620000907;
const int tool_const2=33;
inline ll lldcin()
{
ll tmp=0,si=1;
char c=getchar();
while(c>'9'||c<'0')
{
if(c=='-')
si=-1;
c=getchar();
}
while(c>='0'&&c<='9')
{
tmp=tmp*10+c-'0';
c=getchar();
}
return si*tmp;
}
///Schlacht von Stalingrad
/**Although there will be many obstructs ahead,
the desire for victory still fills you with determination..**/
char str1[1500],str2[1500];
ll dp[1200][1200];
ll solution(ll currentx,ll currenty)//记忆化搜索函数,倒着进行搜索。
{
if(currentx<1||currenty<1)//超出范围,直接返回0
return 0;
ll ans=0;
if(dp[currentx][currenty]>=0)//已经搜索过了,直接返回搜索结果。
return dp[currentx][currenty];
if(str1[currentx]==str2[currenty])//字符相等的话,向左上搜索.
{
ans=solution(currentx-1,currenty-1)+1;
dp[currentx][currenty]=ans;
}
else//否则取正上方和正左方搜索的最大值,并储存。
{
dp[currentx][currenty]=max(solution(currentx-1,currenty),solution(currentx,currenty-1));
}
return dp[currentx][currenty];返回储存值。
}
int DETERMINATION()
{
ll n;
lldin(n);
while(n--)
{
reset(dp,-1);//由于在上边的记忆化搜索需要检测当次结果是否已知,所以初始化是不能为0的。
reset(str1,'\0');
reset(str2,'\0');
getchar();
scanf("%s",str1+1);
scanf("%s",str2+1);
ll len1=strlen(str1+1);
ll len2=strlen(str2+1);
solution(len1,len2);
printlnlld(dp[len1][len2]);
}
return 0;
}
动态规划法+记录路径(这个记录路径的函数可以套回记忆化搜索里)
#include <iostream>
#include <cstdio>
#include <bits/stdc++.h>
#include <map>
#include <algorithm>
#include <stack>
#include <iomanip>
#include <cstring>
#include <cmath>
#define DETERMINATION main
#define lldin(a) scanf("%lld",&a)
#define din(a) scanf("%d",&a)
#define printlnlld(a) printf("%lld\n",a)
#define printlnd(a) printf("%d\n",a)
#define printlld(a) printf("%lld",a)
#define printd(a) printf("%d",a)
#define reset(a,b) memset(a,b,sizeof(a))
const int INF=0x3f3f3f3f;
using namespace std;
const double PI=acos(-1);
typedef long long ll;
typedef unsigned long long ull;
typedef long double ld;
const int mod=1000000007;
const int tool_const=1999112620000907;
const int tool_const2=33;
inline ll lldcin()
{
ll tmp=0,si=1;
char c=getchar();
while(c>'9'||c<'0')
{
if(c=='-')
si=-1;
c=getchar();
}
while(c>='0'&&c<='9')
{
tmp=tmp*10+c-'0';
c=getchar();
}
return si*tmp;
}
///Schlacht von Stalingrad
/**Although there will be many obstructs ahead,
the desire for victory still fills you with determination..**/
char str1[3500],str2[3500];
ll dp[1350][1350],sign[1350][1350];
void display(ll currentx,ll currenty)
这是复现最长公共子序列的内容的函数
{
if(currentx==0||currenty==0)
return ;
else if(sign[currentx][currenty]==1)
{
display(currentx-1,currenty-1);
printf("%c",str1[currentx]);
由于需要正向输出,而这是反向回溯,所以需要把输出放在递归步骤后边,这样就是等递归结束后再一一输出。
}
else if(sign[currentx][currenty]==2)
display(currentx-1,currenty);
else
display(currentx,currenty-1);
}
int DETERMINATION()
{
ll t;
lldin(t);
while(t--)
{
reset(dp,0);
reset(sign,0);
reset(str1,'\0');
reset(str2,'\0');
getchar();
scanf("%s",str1+1);
scanf("%s",str2+1);
ll len1=strlen(str1+1);
ll len2=strlen(str2+1);
for(int i=1; i<=len1; i++)
for(int j=1; j<=len2; j++)
{
if(str1[i]==str2[j])
{
dp[i][j]=dp[i-1][j-1]+1;
sign[i][j]=1;//记录各种回溯方案,1是左上,2是正上,3是正左。
}
else
{
if(dp[i-1][j]>=dp[i][j-1])
{
dp[i][j]=dp[i-1][j];
sign[i][j]=2;
}
else
{
dp[i][j]=dp[i][j-1];
sign[i][j]=3;
}
}
}
display(len1,len2);
printf("\n");
printlnlld(dp[len1][len2]);
}
return 0;
}
空间优化的动态规划法(不可记录路径)
由上面的代码可知,在整个操作过程中只用到了上一行和前一列,所以可以用一个优化把上述M×N的DP数组变成2×max(M,N)的数组。
#include <iostream>
#include <cstdio>
#include <bits/stdc++.h>
#include <algorithm>
#include <iomanip>
#include <cstring>
#include <cmath>
#define DETERMINATION main
#define lldin(a) scanf("%lld",&a)
#define din(a) scanf("%d",&a)
#define printlnlld(a) printf("%lld\n",a)
#define printlnd(a) printf("%d\n",a)
#define printlld(a) printf("%lld",a)
#define printd(a) printf("%d",a)
#define reset(a,b) memset(a,b,sizeof(a))
const long long int INF=0x3f3f3f3f;
using namespace std;
const double PI=acos(-1);
typedef long long ll;
typedef unsigned long long ull;
typedef long double ld;
const int mod=1000000007;
const int tool_const=1999112620000907;
const int tool_const2=33;
inline ll Unnamed_Scanner()
{
ll tmp=0,si=1;
char c=getchar();
while(c>'9'||c<'0')
{
if(c=='-')
si=-1;
c=getchar();
}
while(c>='0'&&c<='9')
{
tmp=tmp*10+c-'0';
c=getchar();
}
return si*tmp;
}
///Schlacht von Stalingrad
/**Although there will be many obstructs ahead,
the desire for victory still fills you with determination..**/
char a[2312323],b[3212323];
ll uuu[1200][1200];
int DETERMINATION()
{
ll n;
lldin(n);
while(n--)
{
reset(a,'\0');
reset(b,'\0');
reset(uuu,0);
getchar();
scanf("%s",a+1);
scanf("%s",b+1);
ll len=strlen(a+1);
ll len2=strlen(b+1);
//cout<<len<<endl;
ll current=1,previous=0;//当前行与上一行
for(int i=1; i<=len; i++)
{
for(int j=1; j<=len2; j++)
{
if(a[i]==b[j])
uuu[current][j]=uuu[previous][j-1]+1;
else
uuu[current][j]=max(uuu[previous][j],uuu[current][j-1]);
}
swap(current,previous);//前一行现在就是当前行,交换。
}
printlnlld(uuu[previous][len2]);
}
return 0;
}

浙公网安备 33010602011771号