动态规划之双进程与二维平面(c++)

本文探讨了如何使用动态规划解决涉及二维平面上的多种问题,如最长公共子序列、最短编辑距离、配置魔药等。文章通过具体题目案例,详细解析了如何将问题转化为二维动态规划的状态转移方程,并提供了相应的代码示例。动态规划方法通过抽象问题,将复杂问题简化为规模较小的子问题,从而有效解决问题。
摘要由CSDN通过智能技术生成

双进程:有两个决策相互影响且同时进行

二维平面:由一般线性的动态规划转移到二维平面之中,思考方式没有太大变化

精髓:拆解较大规模的子问题时,可以拆解成i规模更小,j规模更小,或者i和j规模都减小的子问题,即dp[i][j]通常与dp[i-1][j], dp[i][j-1], dp[i-1][j-1]有关。

题目1

最长公共子序列

题目描述

字符序列的子序列是指从给定字符序列中随意地(不一定连续)去掉若干个字符(可能一个也不去掉)后所形成的字符序列。

令给定的字符序列X=“x0,x1,…,xm-1”,序列Y=“y0,y1,…,yk-1”是X的子序列,存在X的一个严格递增下标序列,使得对所有的j=0,1,…,k-1,有xij = yj。

例如,X=“ABCBDAB”,Y=“BCDB”是X的一个子序列。

对给定的两个字符序列,求出他们最长的公共子序列。

输入格式

第1行为第1个字符序列,都是大写字母组成,以”.”结束。长度小于5000。

第2行为第2个字符序列,都是大写字母组成,以”.”结束,长度小于5000。

输出格式

输出上述两个最长公共自序列的长度。

样例数据

input

ABCBDAB.
BACBBD.

output

4

 首先从公共可以得出这两个字符串都要包含这个子序列

所以我们状态设f[i][j]为x的前i位和y的前j为最长公共子序列的长度

易得(其实不易)

0

i=0,j=0

f [i][j]

max(f[i-1][j-1]+1,f[i][j])

i>0,j>0,xi==yj

max(f[i][j-1],f[i-1][j])

i>0,j>0,xi==yj

代码如下

#include<bits/stdc++.h>
using namespace std;
char ch1[5100],ch2[5100];
int len1,len2;
int f[5100][5100];
int main()
{
	freopen("lcs.in","r",stdin);
	freopen("lcs.out","w",stdout);
	char ch;
	len1=0;
	cin>>ch;
	while(ch!='.')
	{
		ch1[++len1]=ch;
		cin>>ch;
	}
	cin>>ch;len2=0;
	while(ch!='.')
	{
		ch2[++len2]=ch;
		cin>>ch;
	}
	memset(f,0,sizeof(f));
	for(int i=1;i<=len1;i++)
	{
		for(int j=1;j<=len2;j++)
		{
			f[i][j]=f[i][j-1];
			if(f[i][j]<f[i-1][j])
			{
					f[i][j]=f[i-1][j];
			}
			if(ch1[i]==ch2[j]&&f[i][j]<=f[i-1][j-1])
			{
				f[i][j]=f[i-1][j-1]+1;
			}	
		}
	}
	cout<<f[len1][len2];
	return 0;
}

但是我发现了一个非常厉害的转换思想,源于洛谷

、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、

设字符串长度为n,m,那么想象我们有一个n+1行m+1列的网格图,只能从左下角往右、上两个方向走。定义每条路径的长度都为11。记第i行第j列为(i,j)。

求最长公共子序列本质上是在两个序列中寻找最多的配对,而且这些配对的位置在序列中的位置也要分别递增。

那么,如果x_ix与y_j相等,那么我们就从(i-1,j-1)向(i,j)连一条边。这在网格图中分明是一条条捷径,那么我们要寻找最长公共子序列,可不可以转化为寻找最短路,或者说寻找经过捷径次数最多的路径呢?

这个模型是很巧妙的,满足了配对的位置在序列中的位置分别递增(因为只能往右、上走)。

那么再看第二问。显然在这个模型中,不同的公共子序列对应的,不是至少有一条边不相同的路径,而是至少有一条捷径不相同的路径。那么这个该怎么DP呢?

设到达(i,j)(i,j)最多能经过的捷径数(即序列的两个前缀的最长公共子序列长度)为mf_{i,j},方案数为f_{i,j}。显然(i,j)可以从(i-1,j)和(i,j−1)转移,如果x_i=y_j那么还可以从(i−1,j−1)转移(mf加上11)。依次转移,如果新的mf更大则直接覆盖原信息,如果mf相等则f相加。

然而,再次注意不同路径的定义。那么是不是可能存在这样一种情况:到(i−1,j−1)的一条路径,分别转移给了(i-1,j)和(i,j−1),而再一次转移给了(i,j),没有经过不同的捷径,却计算了两遍!显然只有mf_{i-1,j-1}=mf_{i,j}的时候上述情况才会发生,那么这时我们从f_{i,j}​减去f_{i-1,j-1}即可。

、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、

这个方法把抽象的序列实体化,并且用了类似于采花生的思路,很形象

题目2

最短编辑距离

题目描述

给定两个字符串 A 和 B,现在要将 A 经过若干操作变为 B,可进行的操作有:

  • 删除–将字符串 A 中的某个字符删除。
  • 插入–在字符串 A 的某个位置插入某个字符。
  • 替换–将字符串 A 中的某个字符替换为另一个字符。

现在请你求出,将 A 变为 B 至少需要进行多少次操作。

输入格式

第一行包含整数 n,表示字符串 A 的长度。

第二行包含一个长度为 n 的字符串 A。

第三行包含整数 m,表示字符串 B 的长度。

第四行包含一个长度为 m 的字符串 B。

字符串中均只包含大小写字母。

输出格式

输出一个整数,表示最少操作次数。

样例

输入样例1

10 
AGTCTGACGC
11 
AGTAAGTAGGC

输出样例1

4

 如果第一题懂了,这题就是小easy

当前的状况用f[i][j]表示的话就是指1中第i个变成2中第j个的代价

删除

f[i][j]=min(f[i-1][j],f[i][j])

插入

f[i][j]=min(f[i][j-1],f[i][j])

替换

f[i][j]=f[i-1][j-1]+1

注意相等的时候 不一定是不操作,也可能要遵循上一步的操作(之前的插入或者替换)

#include<bits/stdc++.h>
using namespace std;
int a,b;
char s1[1111],s2[1111];
int f[1111][1111];
int main()
{
	freopen("edit.in","r",stdin);
	freopen("edit.out","w",stdout);
	cin>>a;
	for(int i=1;i<=a;i++)
	{
		cin>>s1[i];
	}
	cin>>b;
	for(int i=1;i<=b;i++)
	{
		cin>>s2[i];
	}
	memset(f,10,sizeof(f));
	for(int i=0;i<=a;i++)
	{
		f[i][0]=i;
	}
	for(int i=0;i<=b;i++)
	{
		f[0][i]=i;
	}
	for(int i=1;i<=a;i++)
	{
		for(int j=1;j<=b;j++)
		{
			f[i][j]=min(f[i-1][j]+1,f[i][j-1]+1);
			if(s1[i]==s2[j]) f[i][j]=min(f[i][j],f[i-1][j-1]);
			else f[i][j]=min(f[i][j],f[i-1][j-1]+1);
		}
	}
	cout<<f[a][b];
	return 0;
} 

题目3

配置魔药

题目描述

在《Harry Potter and the Chamber of Secrets》中,Ron的魔杖因为坐他老爸的Flying Car撞到了打人柳,不幸被打断了,

从此之后,他的魔杖的魔力就大大减少,甚至没办法执行他施的魔咒,这为Ron带来了不少的烦恼。

这天上魔药课,Snape要他们每人配置一种魔药(不一定是一样的),Ron因为魔杖的问题,不能完成这个任务,他请Harry在魔药课上(自然是躲过了Snape的检查)帮他配置。

现在Harry面前有两个坩埚,有许多种药材要放进坩埚里,但坩埚的能力有限,无法同时配置所有的药材。

一个坩埚相同时间内只能加工一种药材,但是不一定每一种药材都要加进坩埚里。

加工每种药材都有必须在一个起始时间和结束时间内完成(起始时间所在的那一刻和结束时间所在的那一刻也算在完成时间内),每种药材都有一个加工后的药效。

现在要求的就是Harry可以得到最大的药效。

输入格式

输入文件的第一行有2个整数,一节魔药课的t(1≤t<≤500)和药材数n(1≤n≤100)。

输入文件第2行到n+1行中每行有3个数字,分别为加工第i种药材的起始时间t1、结

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值