蓝桥杯算法入门_11(动态规划 --子序列匹配)

#include<iostream>
using namespace std;

/*常见动态规划模型 --子序列匹配 */

/*
感悟: dp是很好写的,主要是总结 !状态转移方程 !
	代码错误是由多练多错总结记忆,才快速反应的,没遇到过的错误只会无从下手
*/

/*最大子段和
给定一段数组,其中一段连续的序列称为一个子段,(只考虑非空子段)

全部非正数,则取其中的元素的最大值

可以只扫描1次,sum统计 和 ans更新 ,前缀为负数,舍弃

输入:
6
-2 11 -4 13 -5 -2

输出:
20

*/

const int inf_01 = 1000000000;
int ans_01 = -inf_01; //最小保证更新
int sum_01,N_01;
int num_01[101];

void test_01() {	//O(N_01)
	cin >> N_01;
	for(int i = 0 ; i < N_01; i++) {
		cin >> num_01[i];
		ans_01 = max(ans_01,num_01[i]);  //每位元素判断更新
	}
	if(ans_01 <= 0) {
		cout<< ans_01 << endl;//都是负数,选元素的最大值
	} else {
		sum_01 = 0;
		for(int i = 0; i < N_01 ; i++) {
			if(sum_01 + num_01[i] < 0) { //前缀是负数,那么扔掉这个前缀一定更优
				sum_01 = 0; //不选
			} else {
				sum_01 += num_01[i];
			}
			ans_01 = max(ans_01,sum_01); //ans_01更新最大值
		}
		cout<< ans_01 << endl;
	}
	return;
}

/*最长上升子序列  LIS
在原序列取任意多项式 ,不改变他们在原来数列的先后次序 !!! ,得到的序列称为原序列的子序列

最长上升子序列:不断递增的子序列(数值从高到低)

如果有 a[j] < a[i] ,就把 第i项接在 第j项后面

状态转移方程: dp[i] = max( dp[i],dp[j] + 1 ), 1<= j <= i && a[j] < a[i]

输出最长上升子序列的长度
dp数组里的最大值就是最长上升子序列的长度了  !!!

输入:
6
3 2 6 1 4 5

输出:
3

*/


int dp_02[101],a_02[101],n_02;
int LIS_02() {
	int ans = 0;
	for(int i = 1; i <= n_02; i++) {
		dp_02[i] = 1;
		for(int j = 1; j < i; j++) {
			if(a_02[j] < a_02[i]) {
				dp_02[i] = max( dp_02[i] , dp_02[j] + 1 );  //求子序列最大长度的dp_02方程
			}
		}
		ans = max( ans , dp_02[i] );
	}
	return ans;
}

void test_02() {
	cin >> n_02;
	for(int i = 1; i <= n_02; i++) {
		cin >> a_02[i];
	}
	cout << LIS_02() <<endl;


	return;
}

/*最长公共子序列 ,给定两个序列s1和s2,求二者公共子序列s3的最长的长度
s1的前i个字符和s2的前j个字符 的最长公共子序列长度 记作lcs[i][j]

如果s1的第i项 s2的第j项 相同 ,那么s1[i] ,s2[j] 作为公共子序列 的末尾
则 lcs[i][j] =  lcs[i - 1][j - 1] + 1 //长度+1
也可以不让 s1[i] ,s2[j] 作为公共子序列 的末尾
则 lcs[i][j] = max( lcs[i][j - 1],lcs[i - 1][j] )

可证:  max( lcs[i][j - 1],lcs[i - 1][j] ) <=  lcs[i - 1][j - 1] + 1

即    lcs[i][j] = lcs[i - 1][j - 1] + 1   , s1[i] == s2[j]
	  lcs[i][j]	= max( lcs[i][j - 1],lcs[i - 1][j] )  ,  s1[i] != s2[j]


输入:
abcdefgh
acjlfabhh

输出:
4


*/
#include<cstring>
int dp_03[110][110];

void test_03() {
	string a,b;
	memset(dp_03,0,sizeof(dp_03) );
	cin >> a >> b;
	int lena = a.size();
	int lenb = b.size();
	for(int i = 1; i <= lena; i++) {
		for(int j = 1; j <= lenb; j++) {
			if(a[i - 1] == b[j - 1]) {
				dp_03[i][j] = dp_03[i - 1][j - 1] + 1;//i和j可以匹配,加上前面的匹配个数 再加当前匹配 1 位  转移
			} else { //当前位置不相等,不让S1[i]和 S2[j] 作为末尾
				//没有匹配,(前面不会产生新的匹配),考虑分别省略掉一位,看结果是多少 ,转移!       // lcs考虑子序列最后一位一定在这位置上
				dp_03[i][j] = max(dp_03[i - 1][j] ,dp_03[i][j - 1] ) ;
			}
		}
	}
	cout << dp_03[lena][lenb] << endl;
	return;
}


/*给定两个字符串 S 和 T ,对于字符串T ,我们进行下面三种操作

1.在任意位置添加任意字符
2.删除存在的任意字符
3.将一个任意字符修改为另一个任意字符

问经过多少次操作可以将字符串T变成字符串S  (最小)

设S的长度为m ,T的长度为n
简单想法:把T全删了,添加S的全部字符 O(m + n)
优化:把T的长度变为m , 再操作,操作次数最多为 |m - n| + m

(发现搜索不可行,搜索的空间时指数级的,取决于S的字符种类  ,比如S由小写字母组成 ,复杂度就为 O(26 ^ m) )
换一种角度:字符串对齐

1)  S[i] == T[j] ,  答案就是 f(i - 1,j - 1)
2)  S[i] != T[j] ,	那么在当前位置进行修改,因此答案就是  f(i - 1,j - 1)  + 1 //操作 + 1
3)  S的前i位和T的前j-1位对齐之后,在当前位置需要执行一次添加操作 ,因此答案为:f(i,j - 1)  + 1
4) 	S的前i - 1位和T的前j位对齐之后,在当前位置需要执行一次删除操作 ,因此答案为:f(i - 1,j)  + 1

递推式:
dp[i][j] =  dp_04[i][j] = dp_04[i - 1][j - 1];   ,S[i] == T[j]
dp[i][j] =  min(  dp_04[i - 1][j - 1] ,  min(  dp_04[i][j - 1] , dp_04[i - 1][j] )  )  + 1;  ,S[i] != T[j]

边界处理:(对于S的前0个字符,只能把T中的字符全部删掉,同理,对于T中的前0个字符,S只能选择把T中的添加上S当前长度的字符)
	dp[0][j] = j;
	dp[i][0] = i;
时间复杂度、空间复杂度均为 O(m + n)

输入:
abcd
acef


输出:
3


*/
int dp_04[110][110];
string a_04,b_04;
void test_04() {
	cin >> a_04 >> b_04;
	int lena = a_04.size();
	int lenb = b_04.size();
	for(int i = 1; i <= lena; i++) {
		dp_04[i][0] = i;
	}
	for(int i = 1; i <= lenb; i++) {
		dp_04[0][i] = i;
	}
	for(int i = 1; i <= lena; i++) {
		for(int j = 1; j <= lenb; j++) {
			if(a_04[i - 1] == b_04[j - 1]) {
				dp_04[i][j] = dp_04[i - 1][j - 1];//对应位匹配,直接判断下一位,不用操作
			} else { //对应位不匹配 , 找操作少的省略前一位的比较判断
				dp_04[i][j] =  min(  dp_04[i - 1][j - 1] ,  min(  dp_04[i][j - 1] , dp_04[i - 1][j] )  )  + 1;
			}
		}
	}
	cout << dp_04[lena][lenb] << endl;
	return;
}

/*常见动态规划模型练习题 -- 自己盲打一遍 !!!  -- 口述大致思路对应代码  */

/*一排数,求最大非空子段和   test_01


*/

/*一个矩阵 , 求最大非空子矩阵和
思路:枚举 上下边界 , 转换成一维

输入:
3 3
1 -2 3
-4 5 -6
7 -8 9

输出:
9



*/

long long num_05[401][401]; // 1<= N,M <= 400 , -1e9 <= ans <= 1e9
long long presum_05[401][401]; //前一个状态sum

void test_05() {
	int N,M;
	long long sum,ans;
	cin >> N >> M;
	ans = -1000000001;
	for(int i = 1; i <= N; i++) {
		for(int j = 1; j <= M; j++) {
			cin >> num_05[i][j];
			ans = max(ans,num_05[i][j]);
		}
	}
	if(ans <= 0) {
		cout << ans << endl;
	} else {
		for(int i = 1; i <= N; i++) {
			for(int j = 1; j <= M; j++) {
				presum_05[i][j] = presum_05[i - 1][j] + num_05[i][j];
			}
		}
		for(int i = 1; i <= N; i++) {
			for(int j = i; j <= N; j++) { //上下边界 ,N行
				sum = 0;
				for(int k = 1; k <= M; k++) { //从左到右遍历
					if(sum + presum_05[j][k] - presum_05[i - 1][k] < 0) {
						sum = 0;
					} else {
						sum += presum_05[j][k] - presum_05[i - 1][k];
					}
					ans = max( ans,sum );
				}
			}
		}
		cout << ans << endl;
		return;
	}
}

/*环形矩阵的最大空子矩阵


枚举上下左右边界 , 分成4份

整体 - 中间

*/

/*跳木桩
高度h1 -- hn
第一步可以跳到任意一个木桩
之后只能往前跳(不能往回跳)到任意一个木桩 , 且能跳到的下一个木桩的高度 <= 当前木桩
计算最多能跳多少个木桩  (即最长不上升子序列!) --子序列 不改变原序列元素相对顺序 ,取若干项

输入:
7
3 2 6 1 4 5 0

输出:
4


*/



const int MAXN_06 =  1010;
int a_06[MAXN_06];
int dp_06[MAXN_06];


void test_06() {
	int n;
	cin >> n;
	for(int i = 0; i < n; i++) {
		cin >> a_06[i];
	}
	int ans = 0;
	for(int i = 0; i < n; i++) {
		dp_06[i] = 1;//最开始最少单独一个
		for(int j = 0; j < i; j++) {  //每一段所有子序列遍历
			if(a_06[j] >= a_06[i]) {//不上升子序列
				dp_06[i] = max(dp_06[i] , dp_06[j] + 1 );//有不上升(即下降)+1   ,当前dp_06[i]与dp_06[j] + 1 比较,看会不会更长 ,更长就更新
			}
		}
		ans = max(ans,dp_06[i]); //更新答案
	}
	cout << ans << endl;
	return;
}

/*给定n个数 的A序列

删除任意元素
是A变成前一段上升,后一段下降 的序列 (先减后增)
求最少需要被删除的元素

从前往后做一遍不上升子序列子序列 ,再从后往前做一遍不上升子序列  (有最小值)

输入:
输出:

*/

const int MAXN_07 = 2010;
int a_07[MAXN_07];
int dp_07[2][MAXN_07];

void test_07() {
	int n;
	cin >> n;
	for(int i = 0; i < n; i++) {
		cin >> a_07[i];
	}
	for(int i = 0; i < n; i++) {
		dp_07[0][i] = 1;
		for(int j = 0; j < i; j++) {
			if(a_07[j] >= a_07[i]) {
				dp_07[0][i] = max( dp_07[0][i] ,dp_07[0][j] + 1);
			}
		}
	}
	for(int i = n - 1; i >= 0; i--) {
		dp_07[1][i] = 1;
		for(int j = n - 1; j > i; j--) {
			if(a_07[j] >= a_07[i]) {
				dp_07[1][i] = max( dp_07[1][i] ,dp_07[1][j] + 1);
			}
		}
	}
	int ans = 0;
	for(int i = 0; i < n; i++) {
		ans = max(ans , dp_07[0][i] + dp_07[1][i] + 1 );
	}
	cout << n - ans << endl;
	return;
}

/*
最大递增难度和  (上升子序列最大和)

输入:
3
1 3 2

输出:
4

*/

int a_08[1001];
long long sum_08[1001];
int ans_08;
void test_08() {
	int n;
	cin >> n;
	for(int i = 0; i < n; i++) {
		cin >> a_08[i];
	}
	for(int i = 0; i < n; i++) {
		sum_08[i] = a_08[i];//初始 
		for(int j = 0; j < i; j++) {
			if(a_08[j] < a_08[i] && sum_08[j] + a_08[i] > sum_08[i]) { //转移条件 ,递增 && 难度和更大
				sum_08[i] = sum_08[j] + a_08[i];
			}
		}
	}
	for(int i = 0; i < n; i++) {
		if(sum_08[i] > ans_08) {
			ans_08 = sum_08[i];
		}
	}
	cout << ans_08 << endl; 
	return;
}

/*
最长公共子序列

输入:
computer
education 


输出: 
2

*/

#include<algorithm>
#include<cstdio>
#include<string>
const int MAXN_09 = 2010;
int dp_09[MAXN_09][MAXN_09];


void test_09(){
	string a,b;
	cin >> a>> b;
	for(int i = 1;i <= a.size();i++){ //从1开始! 
		for(int j = 1;j <= b.size();j++){
			if(a[i - 1] == b[j - 1] ){
				dp_09[i][j] = dp_09[i - 1][j - 1] + 1;
			}else{
				dp_09[i][j] = max( dp_09[i - 1][j],dp_09[i][j - 1] );
			}
		}
	}
	cout << dp_09[a.size()][b.size()] << endl;
	return;
}

/*字符串变回文串 

等效改操作变相等串问题
即让s与s的反转 相等 


如trit 变 tirit   //可以添加一个i变回文串  

输出最少添加次数 

输入:
trit 

输出: 
1
 

*/
#include<algorithm> 
#include<string>
int dp_10[3005][3005];
string s1_10,s2_10;

void test_10(){
	cin >> s1_10;
	s2_10 = s1_10;
	reverse(s2_10.begin(),s2_10.end()); //反转  让两个字符串一样 
	for(int i = 1;i <= s1_10.size();i++){//从1开始好写 
		for(int j = 1;j <= s2_10.size();j++){
			if(s1_10[i - 1] == s2_10[j - 1] ){//从0开始判断 
				dp_10[i][j] = dp_10[i - 1][j - 1] + 1; //对应位匹配 + 1 
			}else{
				dp_10[i][j] = max( dp_10[i - 1][j],dp_10[i][j - 1] ); //各尝试前面的已经匹配的哪一个更长 
			}
		}
	}
	cout<< s1_10.size() - dp_10[s1_10.size()][s2_10.size()] << endl;   //dp_10[s1_10.size()][s2_10.size()]为最后状态 ,即取了最优解 
	return;
}

/*
被破坏的数据
第一行字符串A ,表示一个本被破坏之后的字符串
第二行字符串B ,表示第二个本上面的字符串 

字符串均小于1000 
可以增删改 ,最少的操作还原 A 变 B(备份) 

套用 test_04()

输入:
aa
ab 

输出: 
1

*/

int dp_11[1001][1001];

void test_11(){
	string A,B;
	cin >> A;
	cin >> B;
	for(int i = 1; i < A.length();i++){ //初始 
		dp_11[i][0] = i;
	}	
	for(int i = 1; i < B.length();i++){
		dp_11[0][i] = i;
	}	
	for(int i = 1;i <= A.length();i++){
		for(int j = 1;j <= B.length();j++){
			if(A[i - 1] == B[j - 1]){
				dp_11[i][j] = dp_11[i - 1][j - 1]; //相等无需操作 ,判断下一位 
			}else{ //不相等,考虑改、 增 (i-1位 +1)、删 (删后少一位,Ai-1与Bj去比较决策)、 三种情况 
				dp_11[i][j] = min (dp_11[i - 1][j - 1] , min( dp_11[i][j - 1], dp_11[i - 1][j] ) ) + 1; //不相等回退 , 选最小次数的操作 + 本次操作 
			}
		}
	} 
	cout << dp_11[A.length()][B.length()] << endl; //最后遍历完,得到最优解的下标  [A.length()][B.length()]
	return;
}




int main() {

	test_11();


	return 0;

}







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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值