字符串、区间DP之 删除字符串、最长回文串、乘积最大(详细分析)

①删除字符串

题目描述

题目描述
给出一个长度为n的字符串,每次可以删除一个字母相同的子串,问最少需要删多少次。 数据规模:n <= 500

输入格式
第1行:1个整数,表示字符串的长度
第2行:n个字符的字符串

输出格式
第1行:1个整数,表示答案

样例

样例输入
5
abaca
样例输出
3

算法分析

此题即为典型的区间DP题,根据题目可以设以 dp[l,r]是为 l 到 r区间删除完字符串的最小次数,可分两种情况讨论:

一般情况下,dp[l,r]由长度可以通过 dp[l+1,r]或 dp[l,r−1]增加一个字符得到,此时取两者之间的最小值。
枚举 k∈(l,r),用 k作为决策点,如果 c[l]=c[k]即可以通过 dp[l][k−1]+dp[k][r]直接得到, 注意此处不需要加一, 因为c[l]和 c[k]是相同字符,所以可以直接删除,而在计算c[l] 和 c[k]时,已经加一所以,不需要加一!

代码

#include
#include
#include
using namespace std;
const int M=1005;
char s[M];
int f[M][M];
int main(){
	int n;
	scanf("%d",&n);
	scanf("%s",&s[1]);
	for(int i=1;i<=n;i++){
		f[i][i]=1;
	}
	for(int len=1;len<=n;len++){
		for(int l=1;l<=n-len;l++){
			int r=len+l;
			if(s[l]==s[r]){
				f[l][r]=f[l+1][r-1]+1;
			}
			else{
				f[l][r]=min(f[l+1][r],f[l][r-1])+1;
			}
			for(int k=l;k<r;k++){
				f[l][r]=min(f[l][r],f[l][k]+f[k][r]-1);
			}
		}
	}
	printf("%d",f[1][n]);
}


②最长回文串


题目描述

题目描述
给定一个字符串 s ,找到 s 中最长的回文子串,输出其长度。
你可以假设 s 的最大长度为 3000。

输入格式
第1行:1个字符串
输出格式
第1行:1个整数

样例

样例输入
babad
样例输出
3

算法分析

这一道题比上一道题要稍简单一些
这一道题主要思想还是 f[l][r] 表示在 lr 这个元区间里最长回文串的长度。所以一共有三种情况:
①当首尾相等 (即 a[l]=a[r] 时),且在 lr 是回文数时
f[l][r]=f[l+1][r-1]+2;
(在这里有一个技巧来判断是否为回文数即 r-l-1

②当首尾相等 (即 a[l]=a[r] 时),但 lr 不是回文数时:
f[l][r]=max(f[l+1][r],f[l][r-1]);

③当首尾不相等时:
f[l][r]=max(f[l+1][r],f[l][r-1]);

代码

#include
#include
#include
using namespace std;
const int M=10005;
int a[M],f[M][M];
char s[M];
void g(int n){
	for(int i=1;i<=n;i++){
		s[i]=s[i-1];
	}
	return ;
}
int main(){
	scanf("%s",&s[1]);
	int n=strlen(s+1);
	for(int i=1;i<=n;i++)
		f[i][i]=1;
	for(int len=2;len<=n;len++){
		for(int l=1;l<=n-len+1;l++){
			int r=l+len-1;
			if(s[l]==s[r]&&f[l+1][r-1]==r-l-1){
				f[l][r]=f[l+1][r-1]+2;
			}
			else{
				f[l][r]=max(f[l+1][r],f[l][r-1]);
			}
		}
	}
	printf("%d",f[1][n]);
}


③乘积最大


题目描述

输入一个长度为N的数字串, 用K个乘号将它分为 (K+1) 个部分,使得得到的乘积最大
例如N = 3 , K = 1,输入的数字串为 312
分法有两种
3*12 = 36
31*2 = 62
最大值为62

输入格式
输入共两行
第一行,正整数 N 和 K
第二行,一个数字串

输出格式
用K个乘号将数字串划分为(K+1)个部分所得到的最大乘积

样例

####样例输入1
3 1
312
####样例输出1
62

####样例输入2
7 3
3314245
####样例输出2
278040

算法分析

这一题与前两道题不一样,不同点在于 f[i][j] 含义的改变,这里的 i 表示一共有 i 个乘号加入到式子里面来;而 j 表示 1~j 这一个区间。

这里有一个字符转换为数的方式

long long get(int i,int j){
	long long ans=0;
	for(int k=i;k<=j;k++){
		ans=ans*10+a[k]-'0';
	}
	return ans;
}

接下来就是状态转移方程

f[i][j] = max{ f(i-1, k) * get(k+1, j) } (1<=i<=m, i+1<=j<=n, i<=k

代码

#include
#include
#include
using namespace std;
const int M=10005;
long long f[M][M];
char a[M];
long long get(int i,int j){
	long long ans=0;
	for(int k=i;k<=j;k++){
		ans=ans*10+a[k]-'0';
	}
	return ans;
}
int main(){
	int n,m;
	scanf("%d %d",&n,&m);
	scanf("%s",a+1);
	for(int i=1;i<=n;i++){
		f[0][i]=get(1,i);
	}
	for(int i=1;i<=m;i++){
		for(int j=i+1;j<=n;j++){
			for(int k=i;k<j;k++){
				f[i][j]=max(f[i-1][k]*get(k+1,j),f[i][j]);
			}
		}
	}
	printf("%lld",f[m][n]);
}

写博客不易,点个赞再走!

你可能感兴趣的:(dp,字符串)