最长回文子串

天下英雄苦回文串久矣!
先来一个简单点儿的练练手
求最长回文子串
题目来源:PTA 团体程序设计天梯赛-练习集!

题面
在这里插入图片描述
第一次做这个题的时候只想到了暴力的做法,还在傻傻的暴力
还没暴力出来!我淦!
我真傻,真的!
但是实际上有一种DP的方法十分简单!

简单的DP思路
我们先随意假设一个字符串 “ababc”
我们先把目光放在中间的 “a” 上(即第三个字符a)
一个单一的字符一定是一个回文串
那么我们以a为起点,向两边扩散
发现 a左右两边都是 b,则 bab 也是一个回文串!
接着往下找,发现 a 和 c 不相等了 ,所以 ababc 就不是一个回文串!

所以状态转移方程:
枚举字符串的起始点 和长度就可以表示一个子串
然后让长度 <=1的子串(包括长度为0的串)
的dp值为1(默认他们就是回文串)
然后对后续的长子串进行状态转移!

dp[i][j] → if(t[i]==t[j]) dp[i][j] | = dp[i+1][j-1]

其中dp[i][j]中的i,j分别表示 字符串的起点和终点 ,用起点和终点来表示我们的子串!对于dp[i+1][j-1]来说。如果他是回文串,且我们往外左右各自扩散一位时,t[i]==t[j], 则dp[i][j]也是回文串。

并且我们发现:长度为0的串的状态可以转移到长度为2的串上!
长度为1的串的状态可以转移到长度为3的串上!
依次加2加2加2.。。。。。以此类推!
就这么简单!

#include<bits/stdc++.h>
using namespace std;
int dp[1501][1501];
int main()
{
	string t;
	getline(cin,t);
	int len=t.size();
	int mmax=-1;
	for(int i=0;i<=len;i++)//枚举长度! 
	  for(int begin=0;begin+i<=len;begin++)//枚举起点! 
	  {
	  	int end=begin+i-1;//终点! 
	  	if(i<=1)
	  	dp[begin][end]=1;
	  	else
	  	{
	  		dp[begin][end]=0;
	  		if(t[begin]==t[end])
	  		{
	  		 dp[begin][end]|=dp[begin+1][end-1];
			}
		}
	    if(dp[begin][end])
	    {
	    	mmax=max(mmax,i);
		}
	  }
	cout<<mmax;
	return 0;
}

现在我们来试一个稍微难一点儿的

合并回文子串
题目来源:牛客网

题面
链接:https://ac.nowcoder.com/acm/problem/13230
来源:牛客网

输入两个字符串A和B,合并成一个串C,属于A和B的字符在C中顺序保持不变。如"abc"和"xyz"可以被组合成"axbycz"或"abxcyz"等。
我们定义字符串的价值为其最长回文子串的长度(回文串表示从正反两边看完全一致的字符串,如"aba"和"xyyx")。
需要求出所有可能的C中价值最大的字符串,输出这个最大价值即可
输入
第一行一个整数T(T ≤ 50)。
接下来2T行,每两行两个字符串分别代表A,B(|A|,|B| ≤ 50),A,B的字符集为全体小写字母。
输出
对于每组数据输出一行一个整数表示价值最大的C的价值。
样例
输入
2
aa
bb
a
aaaabcaa
输出
4
5

------------------------------------------分割线-------------------
我们刚才DP一个字符串用了那种思想
我们现在想DP一个两个字符串组合起来的新字符串

由于这个题规定字符串长度小于等于50,我们直接就(很快啊)
把dp从二维扩展到思维
dp[ ][ ][ ][ ]
dp[begin1][en1][begin2][end2]
设两个串 分别 为 a b 串
则我们选取的是,a中 ,begin1-en1 的这些字符!
b 中 begin2-end2的这些字符!

我们还是把长度 《=1的字符串的dp值设置为1
长度= a的长度+b的长度
然后进行状态DP

第一个问题
我们从a中选出若干字符,从b中选出若干字符来组合
但是组合的顺序是什么?

dp[begin1][en1][begin2][end2]到底表示那个字符串
其实这个问题不用考虑
比如说 我们从 a 中选出 串“aa" 从 b中选出 串 “bb"
则 他们会有很多种排序
: abba baab
aabb baba abab bbaa …
只要这些组合中有一种是回文串,我们的这个
dp值就会被赋值成1,题目也没让我们输出字符串,
所以不用考虑这个问题。

拿 abba 这个字符串来说
转移顺序:
空串 → bb → abba

具体的转移过程
这个状态转移不同于之前的一个字符串的转移:
因为我们需要确定 左右 新加过来的两个字符 都是 属于 那个串的。(不同于之前一个题,新加的两个字符一定都是原串中的字符)

现在我们是从两个字符串中(a,b)新选两个字符 加在我们之前已经确定的回文串上

情况讨论
这两个字符可能都来自 串 a
取自于 当前a子串的两端
这两个字符可能都来自 串 b
取自于 当前b子串的两端
这两个字符可能一个来自串a,一个来自串b
( 在a 子串前面取一个,在b子串后边取一个)
(在 a子串后面取一个,在b子串前面取一个)
现在我们思考一下,为什么不能a后边取一个,b后面取一个
或者a前面取一个,b前面取一个呢 ,因为一旦这样取,不管a,b怎么放,都会导致新产生的字符串违背 “顺序不变”的原则!!!

所以状态转换一共四种情况!

#include<bits/stdc++.h>
using namespace std;
const int N=55;
int f[N][N][N][N];
int t;
char a[N],b[N];
int main()
{
	scanf("%d",&t);
	while(t--)
	{
		scanf("%s %s",(a+1),(b+1));
		int ans=-1;
		int lena=strlen(a+1);
		int lenb=strlen(b+1);
		for(int la=0;la<=lena;la++) 
			for(int lb=0;lb<=lenb;lb++)
			{
				for(int sa=1;sa+la-1<=lena;sa++)
				{
					for(int sb=1;sb+lb-1<=lenb;sb++)
					{
						int ea=sa+la-1;int eb=sb+lb-1;
						if(la+lb<=1) f[sa][ea][sb][eb]=1;
						else
						{
							f[sa][ea][sb][eb]=0;
							if(a[sa]==a[ea]) f[sa][ea][sb][eb]|=f[sa+1][ea-1][sb][eb];//让两个a做边界 
							if(b[sb]==b[eb]) f[sa][ea][sb][eb]|=f[sa][ea][sb+1][eb-1];//让两个b做边界 
							if(a[sa]==b[eb])  f[sa][ea][sb][eb]|=f[sa+1][ea][sb][eb-1];//让a,b做边界,但是一前一后!!!!!! 
							if(a[ea]==b[sb]) f[sa][ea][sb][eb]|=f[sa][ea-1][sb+1][eb];	   
						}
						//printf("%d %d %d %d %d\n",sa,ea,sb,eb,f[sa][ea][sb][eb]);
						if(f[sa][ea][sb][eb])  ans=max(ans,la+lb);
					}
				}
			}
		cout<<ans<<endl;
	 } 
}

  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值