一、数字三角形
求从三角形顶端到底端的最大值。注意边界的初始化。状态的表示是到ij位置的最大值是多少。
#include<bits/stdc++.h>
using namespace std;
// 898数字三角形
const int N=510,inf=1e9;
int n;
int f[N][N];
int s[N][N];
int main()
{
cin>>n;
int maxx=-inf;
for(int i=1;i<=n;i++)
for(int j=1;j<=i;j++)
cin>>s[i][j];
//因为三角形上可能是负数,但是数组上0,就会一直不变
//而且初始化要注意边界问题,可能一个数左边没路但是是0,右边有路但是为负数
for(int i=0;i<=n;i++)
for(int j=0;j<=i+1;j++)
f[i][j]=-inf;
f[1][1]=s[1][1];
for(int i=2;i<=n;i++)
{
for(int j=1;j<=i;j++)
{
f[i][j]=max(f[i-1][j-1]+s[i][j],f[i-1][j]+s[i][j]);
if(i==n)maxx=max(maxx,f[i][j]);
}
}
cout<<maxx;
return 0;
}
二、最长上升子序列问题
并利用前驱数组输出序列。
#include<bits/stdc++.h>
using namespace std;
// 895 最长上升子序列
const int N=1000;
int a[N];
int b[N];
int p[N];
int main()
{
int n;
cin>>n;
for(int i=1;i<=n;i++)
cin>>a[i],p[i]=0;
b[1]=1;
for(int i=2;i<=n;i++)
{
/*int flag=0;
for(int j=1;j<i;j++)
{
if(a[i]>a[j])b[i]=max(b[i],b[j]+1),flag=1,p[i]=j;
}
if(!flag)b[i]=1;*/
//之前弄错了,认为1号没有前驱,把p1的前驱弄成1了,导致后面判断是否到头的时候陷入死循环。
b[i]=1;
for(int j=1;j<i;j++)
{
//只有在更新以i结尾的最小子序列的时候才要更新p,而不是看到i大于j就更新。
if(a[i]>a[j])
{
if(b[i]<b[j]+1)
{
p[i]=j;
b[i]=b[j]+1;
}
}
}
}
int res=0;
for(int i=1;i<=n;i++)
if(b[i]>b[res])res=i;
cout<<"最长为"<<b[res]<<endl;
cout<<"路径为:"<<endl;
while(res)
cout<<a[res]<<" ",res=p[res];;
return 0;
}
三、最长公共子序列问题
求两个子串的最长公共不连续子序列。
状态表示:a串中前i个和b串中前j个的最长公共子序列
集合划分后的表示:
由于是求的最大值,第二块热狗并不是b串的第j位置必须包含在子集合里面。而是还包含f(i-1,j-1)的情况。
其实最后f的表示中 f[i-1,j] f[i,j-1]是完全包含了f[i-1,j-1]的。
代码实现如下:注意输入字符串的时候,想要从1号下标开始的做法。
scanf("%s",a+1);
#include<bits/stdc++.h>
using namespace std;
// 897 最长公共子序列
const int N=1010;
int a[N],b[N];
int f[N][N];
int main()
{
int n,m;
cin>>n>>m;
scanf("%s%s",a+1,b+1);
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
f[i][j]=max(f[i-1][j],f[i][j-1]);
if(a[i]==b[j])f[i][j]=max(f[i][j],f[i-1][j-1]+1);
}
}
cout<<f[n][m]<<endl;
}
四、石子合并(区间dp)
给出多堆石子儿,每次两两进行合并,而且只能合并相邻的堆,求最小代价。
最后一定是将两堆合并成一堆,只不过这两堆各自也是由两堆合并。只有把底层所有区间的最优状态一步一步构成。所以在循环的时候按照区间长度由小到大来做。
状态集合:
代码实现:在求min的时候要注意f[ l , r ]的初始化
#include<bits/stdc++.h>
using namespace std;
// 897 最长公共子序列
const int N=1010;
int f[N][N];
int s[N];
int n;
int main()
{
cin>>n;
for(int i=1;i<=n;i++)cin>>s[i],s[i]+=s[i-1];
//从小到大枚举长度
for(int len =2 ;len<=n; len++ )
{
//枚举各个长度下的起点ffr
for(int i=1;i+len-1<=n;i++)
{
int l=i,r=i+len-1;
//注意这里只是先让lr为一个较大的数,而不是lk,所以lk为0
f[l][r]=1e9;
//枚举区间界限位置
for(int k=l;k<r;k++)
{
f[l][r]=min(f[l][r],f[l][k]+f[k+1][r]+s[r]-s[i-1]);
}
}
}
cout<<f[1][n];
return 0;
}