关于二维DP————站上巨人的肩膀
作者:互联网
意匠惨淡经营中ing,
语不惊人死不休........
前几天学了DP,做了个简单的整理,记录了关于DP的一些概念之类的,今天记录一下刚学的一个类型
————关于二维DP
那建立二维数组主要是干嘛用的呢???其实就是是记录两个状态,(我也不是很清楚),然后再递推
直接上题吧
T1 、最长公共子序列:
好像有印象,之前做过一道类似的题,叫做最长上升子串,需要注意的是,这俩可不是很一样,人家子序列可以不连续,只要是相对顺序不改变就行,但是子串必须是连续的,针对这种题,我们联想起原来做题时想的状态转移方程,再加以改动就可以了:
f[i][j] 还是表示数组a的前i个元素和b数组的前j个元素能组成的最长子串的长度
那很容易以相同的方法联想到子序列,还是分情况讨论:
1、x[i]==y[j]时:f[i][j]=f[i-1][j-1]+1 (就是它们如果相等,长度就是不加上它们时组成的子串的长+1)
2、x[i]!=y[j]时:
1 2 3 4 5 和 2 3 5,这两个子串在 i=4,j=2 的时候,由于a[i]!=b[j],加上它们俩和不加他们俩其实并不影响最长长度,所以i和j对应的他们以前这些元素(不包括i,j)能组成的最长长度其实和它们现在(包括i,j)能组成的长度是相同的(不必+1),所以当a[i]!=b[j],它们对应的长度可以是f[i-1][j],也可以是f[i][j-1],取个max就好
再考虑边界,当一个字符串有0个元素时,它们的f[i][j]永远是0,所以f[0][j]=0 ,f[i][0]=0。
代码如下:
1 #include<iostream> 2 #include<cstdio> 3 #include<cmath> 4 #include<cstring> 5 using namespace std; 6 int f[1001][1001]; 7 char x[1001],y[1001]; 8 int maxn; 9 int main() 10 { 11 cin>>x; 12 cin>>y; 13 int m=strlen(x); 14 int n=strlen(y); 15 for(int i=0;i<m;i++) 16 for(int j=0;j<n;j++) 17 { 18 f[i][0]=0; 19 f[0][j]=0; 20 } 21 for(int i=1;i<=m;i++) 22 { 23 for(int j=1;j<=n;j++) 24 { 25 if(x[i-1]==y[j-1]) f[i][j]=f[i-1][j-1]+1;//人家是从0开始纳入的,所以这里要-1 26 else f[i][j]=max(f[i-1][j],f[i][j-1]); 27 maxn=maxn>f[i][j]? maxn:f[i][j]; 28 } 29 } 30 printf("%d",maxn); 31 return 0; 32 }
T2、编辑距离:
题意:有两个字符串,现在有三种操作:1、删除一个字符,2、插入一个字符,3、将一个字符改成另一个字符,现在要求要么花最少的字符操作次数,将字符串A转成B(只能操作A串)。
还是先设变量,f[i][j]表示把x[1~i]变为y[i~j]的最少操作次数,现在的目标就是求出状态转移方程:
还还还是分两种情况讨论:
当 x[i]==y[j] 时,那我们就可以不对它们进行操作了,也就是说我们直接让f[i][j]=f[i-1][j-1]
当 x[i]!=y[j] 时,我们有三种操作,需要分别求出他们的状态转移方程
1、删除 x[i],那就是上一次的操作次数再加上1,即 f[i][j]=f[i-1][j]+1
2、给x[i]插入新字符:那就是相当于给y[j]删去一位,即 f[i][j]=f[i][j-1]+1
3、将x[i]变为y[j]:目前a数组的前i位和b数组的前j位都一样了,那其实就是给上一次的状态的基础上再操作一次,即f[i[[j]=f[i-1][j-1]+1
还有就是边界条件:当有一个数组为空时,我就全部插入(x数组为空),或全都输出(y数组为空),即 f[i][0]=i ,f[0][j]=j
代码如下:
T3、机器分配问题(难得要命)
题目差不多就是说现在有n个公司,一共有m台,现在输入了一个n*m的矩阵,分别代表第i个公司如果分配j台机器的话的盈利,现在求最大盈利值,并输出每个公司应分配多少台机器
首先设一个二维数组f[i][j]代表前i个公司分配j台设备的最大盈利,那我们可以把f[i][j]分为两个阶段:
那就是把状态 i 分为前 i-1个公司和第 i 公司,设前 i-1 个公司分配了k个机器,那第 i 个公司就剩下了 j-k 台,我们让k从0到 j 挨个取一遍,求出最大值就好了,那状态转移方程就写出来了:
for( k=0 ; k<=j ; k++)
if ( f[i][j] <= f[i-1][k]+a[i][j-k] )
f[i][j] = f[i-1][k] + a[i][j-k]
那么我们这个a[i][j]咋来呀,仔细想一想,a[i][j]不就表示的是第 i 个公司分配 j 台机器的盈利吗,那就是我们要输入的那个二维数组呀!!!
那边界是啥???
当 i =0 时,f[0][j] 相当于 0 个公司分配 j 台机器,那最大利润就是 0 (连公司都没有,肯定不会有利润),即 f[0][j]=0
当 j =0 时,f[i][0] 相当于 i 个公司分配 0 台机器,那最大利润就是 0 (连机器都没有,肯定不会有利润),即 f[i][0]=0
你以为就这样愉快地结束了吗???
还要输出每个公司分配的机器数量呢,和原来咱求出最长上升子序列一样,咱还要记录用一个数组记录前驱,用于最后输出
实现起来就是这样的,如果 f[i][j] <= f[i-1][k]+a[i][j-k],那么在给f[i][j]重新赋值的时候,我们顺便记录一下前驱,即p[i][j]=k,(代表前i-1个公司要用多少台机器),之所以定义它是为了记录他的前驱。在输出最大利润之后,从n到1开始递减,定义一个t=m(就是t台机器),建立一个ans数组来记录答案,第n个公司(也就是最后一个)会用掉p[i][t]台机器,所以ans[i]=p[i][t],再让t=t-p[i][t],就是除了第n个公司其他公司用的机器数量,重复以上操作,我就得到了每个公司的ans,再用for循环跑一遍输出ans就好了
代码放下面了
1 #include<iostream> 2 #include<cstdio> 3 #include<cmath> 4 #define int long long 5 using namespace std; 6 int ans=100000; 7 int a[1001][1001],f[1001][1001],p[1001][1001],ans1[1001]; 8 //a用于记录数据,f[i][j]代表前i个公司用了j台机器,p是代表第i个公司用了多少台电脑,ans1是答案 9 signed main() 10 { 11 int n,m; 12 scanf("%lld%lld",&n,&m); 13 for(int i=1;i<=n;i++){ 14 for(int j=1;j<=m;j++){ 15 scanf("%lld",&a[i][j]);} 16 } 17 for(int i=0;i<=n;i++){ 18 for(int j=0;j<=m;j++){ 19 f[0][j]=0; 20 f[i][0]=0;}//边界条件 21 } 22 for(int i=1;i<=n;i++) 23 { 24 for(int j=0;j<=m;j++) 25 { 26 for(int k=0;k<=j;k++) 27 { 28 if(f[i][j]<f[i-1][k]+a[i][j-k])//要不要换 29 { 30 f[i][j]=f[i-1][k]+a[i][j-k];//换! 31 p[i][j]=k;//记录第前i-1个公司用几台机器 32 } 33 } 34 } 35 } 36 cout<<f[n][m]<<endl;//输出最大利润 37 int t=m;//相当于一共t台机器 38 for(int i=n;i>=1;i--) 39 { 40 ans1[i]=t-p[i][t];//记录第i个公司用了多少台 41 t=p[i][t];//前i-1个公司用了多少台 42 } 43 for(int i=1;i<=n;i++){ 44 cout<<i<<" "<<ans1[i]<<endl;//优美的输出 45 } 46 return 0;//结束 47 }
T4、乘积最大(这是嫖的旁边的大佬的,所以这道题我是以大佬做题时思考的角度来写的)
直奔正题,分为几个步骤:
1、建立几个数组:①a数组代表我们想要得到的输入的数组,②f[i][j]代表1~i之间有j个乘号时状态的最大值
2、读入数组: 读题发现输入的是一个字符数组,遇到不要慌,我只要把它转成数字数组就好了,然后就写一个read函数来记录下来想要的a数组。
3、首先记录如果有0个乘号,那么那我们记录下来如果没有称号的话从1到i组成的 i 位数,
也就是
1 for(int i=1;i<=n;i++){ 2 f[i][0]=f[i-1][0]*10+a[i]; 3 }
4、现在我们来分析一下:
我们首先要明确一个前提,就是阶段后面都有一个乘号,而一个阶段里是只有数字的。
现在我已经处理到第f[i][j]这个状态了,画一个图:
这里有 j-1个乘号 后面都是数字,组成一个数字 当前的第i个数字
|————*...*...*....*——————————*|———————............——————————————|(一个阶段后面必然有一个乘号)
1 前j-1个乘号 l,也就是第j个乘号的位置 当前状态:第i个数前面有j个1乘号
目前i前面有j个乘号,我们把这些乘号分成两段,一段是前 j - 1个乘号,另一段是第 j 个乘号,因为每隔一个乘号就会有两个数,所以前 j-1 个乘号就会有j个数,也就是说,状态f[i][j]前的那个乘号一定会在第j个数(能取到j)和第i个数(不能取到i)之间,而我们把从 i 到 j 跑循环用的变量设为 l,那么j <= l < i,这个发现会对找出状态转移方程发挥巨大作用,现在的目标就是找出状态转移方程:
当前状态取了乘号,那就是上一个取乘号的阶段乘上这一阶段的数,首先定义一个函数wucheng,这里代表从上一个乘号到下一个乘号之间的数字组合成的一个多位数(一位数字也有可能),前面的状态是前 l个数字(为啥是前 l 个数字?看图)有j-1个乘号(j-1的原因是因为这个状态我又取了一个乘号,总共有j个,那前面的阶段就是j-1个),那就是f[l][j-1],而从上个乘号到这个乘号之间的数就是从第 l-1 位到第 i 位的每个数字组成的一个数字wucheng(l+1,i),他们俩一乘就是取了乘号后的状态,所以得到方程
f[i][j] = max( f[i][j],f[l][j-1] * wucheng(l+1,i))
还有一点需要注意的是, j 不能无限加下去,一共只有 k 个乘号,所以 j 必须小于等于k,然后就没事了
OK,上代码
1 #include<iostream> 2 #include<cmath> 3 #include<cstdio> 4 #define ll long long 5 using namespace std; 6 ll n,k;//k代表总共输入多少乘号,n代表一共输入多少个数字 7 ll a[50]; 8 void read() 9 { 10 char b=getchar(); 11 while(b<'0'||b>'9') 12 { 13 b=getchar(); 14 } 15 int cnt=1; 16 a[cnt]=b-'0'; 17 while(cnt<=n) 18 { 19 b=getchar(); 20 a[++cnt]=b-'0'; 21 }//得到数组a 22 } 23 int f[50][10]; 24 int wucheng(int i,int j) 25 { 26 int cnt=0; 27 for(int l=i;l<=j;l++) 28 { 29 cnt*=10; 30 cnt+=a[l]; 31 } 32 return cnt; 33 }//定义这个函数,是为了记录从l到i之间的数字组成的那一个多(单)位数 34 int main(){ 35 scanf("%d%d",&n,&k); 36 read(); 37 for(int i=1;i<=n;i++) 38 { 39 f[i][0]=f[i-1][0]*10+a[i]; 40 } 41 for(int i=2;i<=n;i++)//从第一个数到最后一个数挨个枚举 42 { 43 for(int j=1;j<=i-1&&j<=k;j++)//乘号个数,不能多余k个 44 { 45 for(int l=j;l<i;l++) 46 { 47 f[i][j]=max(f[i][j],f[l][j-1]*wucheng(l+1,i)); 48 }//l后面就没有乘号了,挨个枚举l,就能得到最优解 49 //分为两种情况,一是这个状态后面加乘号,一种是不加 50 } 51 } 52 cout<<f[n][k]; 53 return 0; 54 }
T5、复制书稿
题目描述:输入两个数分别表示m本书和n个人抄,第二行输入m个数,表示每本书抄写所用的时间,现在让这n个人同时抄这些书,求如何分配才能使得总时间最少,要求:每个人抄的书几本必须是连续的,而且希望第一个人抄的最少,输出一共n行,每行代表第几个人抄的书是从第几本到第几本,中间用一个空格分隔
觉得这道题好像跟机器分配还是有些像的,还是设 f[i][j] 为前i个人抄写前j本书花的时间的最小值,也还是设前 i-1 个人抄了前 l 本书,第 i 个人抄了从第 l+1 到第 j 本书,再设一个二维数组 d来表示从抄第1本书到第 j 本书所用的时间,也就是前缀和,我需要提前求的
1 #include<iostream> 2 #include<cstdio> 3 #define ll long long 4 using namespace std; 5 ll m,k,a[5001],f[5001][5001],d[5001]; 6 void print(ll i,ll j){ 7 ll t,x; 8 if(j==0)return 0; 9 if(j==1) 10 { 11 printf("1 %lld\n",i); 12 return; 13 } 14 t=i; 15 x=a[i]; 16 while(x+a[t-1]<=f[k][m]) 17 { 18 x+=a[t-1];//找到一个恰好小于最小值的 19 t--;//寻找此段的起始编号 20 } 21 print(t-1,j-1); 22 printf("%lld %lld\n",t,i); 23 return 0; 24 } 25 int main() 26 { 27 scanf("%lld%lld",&m,&k); 28 for(ll i=0;i<=500;i++) 29 { 30 for(ll j=0;j<=500;j++) 31 { 32 f[i][j]=0xfffffff; 33 //初始每一个为一个很大的数,以便后面替换 34 } 35 } 36 for(ll i=1;i<=m;i++) 37 { 38 scanf("%lld",&a[i]); 39 d[i]=d[i-1]+a[i]; 40 //前缀和(大概 41 f[1][i]=d[i]; 42 //一个人抄下所有书的情况 43 } 44 for(ll i=2;i<=k;i++){ 45 //枚举每一个人 46 for(ll j=1;j<=m;j++){ 47 //枚举每一本书 48 for(ll l=1;l<=j-1;l++){ 49 //从前往后枚举,查看哪一个最少 50 if(max(f[i-1][l],d[j]-d[l])<f[i][j]) 51 f[i][j]=max(f[i-1][l],d[j]-d[l]); 52 } 53 } 54 } 55 print(m,k);//输出 56 return 0; 57 }
小生才疏学浅,孤陋寡闻,记录的东西也不算多好,以后会查缺补漏,愿日臻完善!
也十分感谢zyb大哥的帮助,这些题的思路包括代码实现都是人家资助的
再见!2022/3/20
标签:巨人,机器,int,ll,二维,1001,数组,include,DP 来源: https://www.cnblogs.com/zch061023/p/16020960.html