数塔
dp[i][j] = max(dp[i+1][j],dp[i+1][j+1])+f[i][j];
最大连续子序列和
dp[i]记录以A[i]为末尾的连续序列最大和。
状态转移方程:dp[i] = max{A[i], dp[i-1]+A[i]}
#include<stdio.h>
int n,data[1010],dp[1010];
int main(){
while(scanf("%d",&n)!=EOF){
for(int i=0;i<n;i++){
scanf("%d",&data[i]);
}
dp[0] = data[0];
int ans=-1;
for(int i=0;i<n;i++){
if(dp[i-1]+data[i]>data[i]){
dp[i] = dp[i-1]+data[i];
}
else{
dp[i] = data[i];
}
if(dp[i]>ans)
ans = dp[i];
}
printf("%d\n",ans);
}
return 0;
}
最长不下降子序列
dp[i]表示以A[i]结尾的最长不下降子序列长度。
状态转移方程
dp[i] = max{1,dp[j]+1} (j=1,2,…,i-1&&A[j]<A[i])
边界:dp[i] = 1(1<=i<=n)
#include<stdio.h>
#include<string.h>
int main(){
int A[100],l;//n记录数组长度,A[n]记录数据
int dp[100] = {0};
while(scanf("%d",&l)!=EOF){
for(int i=0;i<l;i++){
scanf("%d",&A[i]);
}
int ans=-1;
for(int i=0;i<l;i++){
dp[i]=1;
for(int j=0;j<i;j++){
if(A[i]>=A[j]&&dp[i]<dp[j]+1){
dp[i] = dp[j] + 1;
if(dp[i]>ans){
ans=dp[i];
}
}
}
}
printf("%d\n",ans);
}
return 0;
}
最长公共子序列
dp[i][j]记录字符串A的i号位和字符串B的j号位之前的LCS长度
(注意解法中特殊的输入技巧
状态转移方程:
dp[i-1][j-1] + 1;A[i]==B[j]
//A,B串不同i-1,j-1
dp[i][j] =
max{dp[i-1][j],dp[i][j-1]};A[i]!=B[j]
边界:dp[i][0] = dp[0][j] = 0 (0<=i<=n,0<=j<=m)
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
int dp[100][100];
char A[100],B[100];
int main(){
while(scanf("%s%s",A+1,B+1)!=EOF){//特殊技巧A+1 从第1个元素开始读取
int l1=strlen(A+1),l2=strlen(B+1);//计算长度时也从1开始 所以要循环到length才可
for(int i=0;i<=l1;i++)
dp[i][0] = 0;
for(int j=0;j<=l2;j++)
dp[0][j] = 0;
for(int i=1;i<=l1;i++){
for(int j=1;j<=l2;j++){
if(A[i]==B[j])
dp[i][j] = dp[i-1][j-1] + 1;
else{
dp[i][j] = max(dp[i-1][j],dp[i][j-1]);
}
}
}
printf("%s %s %d\n",A+1,B+1,dp[l1][l2]);
}
return 0;
}
最长回文子串
dp[i][j]表示s[i]至s[j]所表示的串是否为回文串,是则为1不是则为0。
状态转移方程:
dp[i+1][j-1],s[i]==s[j]
//满足看子串
dp[i][j] =
0,s[i]!=s[j]
边界: dp[i][i] = 1,dp[i][i+1] = (s[i] == s[i+1])?1:0;
特殊递归写法:
按子串长度和子串初始位置进行枚举,及第一遍求长度为3的子串,第二遍求长度为4的子串。。。
//s为串
int len = strlen(s),ans=1;
memset(dp,0,sizeof(dp));//dp数组初始化为0
//边界
for(int i=0;i<n;i++){
dp[i][i] = 1;
if(i<len-1){
if(s[i]==s[i+1]){
dp[i][i+1] = 1;
ans = 2;
}
}
}
//状态转移方程
for(int L=3;L<=len;L++){//枚举子串长度
for(int i=0;i+L-1<len;i++){//枚举子串的起始端点
int j=i+L-1;//子串的右端点
if(s[i] == s[j] && dp[i+1][j-1] == 1){
dp[i][j] = 1;
ans = L;
}
}
cout<<ans<<endl;
}
或者
#include<stdio.h>
#include<string.h>
int dp[100][100];
int main(){
char str[100];
int len,ans;
memset(dp,0,sizeof(dp));
while(scanf("%s",str)!=EOF){
len = strlen(str);
for(int i=0;i<len;i++){
dp[i][i]=1;
ans=1;
}
for(int i=0;i<len-1;i++){
if(str[i]==str[i+1]){
dp[i][i+1] = 1;
ans=2;
}
else{
dp[i][i+1] = 0;
}
}
for(int l=3;l<=len;l++){
for(int i=0;i+l-1<len;i++){
int j=i+l-1;
if(str[i]==str[j]&&dp[i+1][j-1]==1){
dp[i][j] = 1;
ans = l;
}
else{
dp[i][j] = 0;
}
}
}
printf("%d\n",ans);
}
return 0;
}
背包
有一个容量为v的背包和一些物品。这些物品分别有两个属性,体积为w和价值为v。要求用这个背包装下价值尽可能多的物品,求其最大价值。
01背包问题(有穷件
dp[i][j]表示总体积不超过j的情况下,前i个物品能达到的最大价值。
状态转移方程:
dp[i][v] = max{dp[i-1][v],dp[i-1][v-w[i]]+c[i]};
//max(不装入该物品所能达到的最大价值,装入该物品所能达到的最大价值);
特别注意判断v-w[i]是否为负,若是,则不能被转移。
边界:dp[0][v] = 0;(0<=v<=V)
注意:每个状态只和上一个状态有关,特别进行空间优化
dp[v] = max{dp[v],dp[v-w[i]]+c[i]}
变为一维数组后,为了保证使用数据不变(该物品只能被取一次,枚举方向必须逆序
代码如下:
#include<stdio.h>
#include<algorithm>
using namespace std;
const int maxn=100;
int dp[maxn];
struct thing{
int u;//体积
int w;//质量
};
thing t[maxn];
int main(){
int data[maxn];
int v,n;//n为数组长度
while(scanf("%d%d",&n,&v)!=EOF){
for(int i=0;i<n;i++){
scanf("%d%d",&t[i].u,&t[i].w);
}
for(int i=0;i<v;i++){
dp[i] = 0;
}
for(int i=0;i<n;i++){
for(int j=v;j>=t[i].u;j--){
dp[j] = max(dp[j],dp[j-t[i].u]+t[i].w);
}
}
printf("%d\n",dp[v]);
}
return 0;
}
形成自己的解题风格
struct thing{
int v;
int w;
}
记录物品信息
0-1背包变化问题: 所选择的物品必须恰好装满背包
此时dp[i][j]必须保证体积和为j
解决方法:
改变初始状态
初始状态变为dp[0][0]为0,而其他dp[0][j]均变为负无穷。
理由如下:(观看某大神解
由f[j]=Math.max(f[j],f[j-w[i]]+v[i])知,第i步的最优解都是根据第i-1步推得,要想第i步获得的结果恰好装满,那么第i-1步也必须是恰好装满的最优解,否则第i-1的解也应该为Integer.minValue,因为Integer.minValue+W[i]=Integer.minValue。
完全背包(无穷件
状态转移方程:
dp[i][v] = max(dp[i-1][v],dp[i][v-w[i]]+c[i]);
边界:dp[0][v] = 0;
改成一维形式:
dp[v] = max(dp[v],dp[v-w[i]]+c[i]);
边界:dp[v] = 0;
此时有无穷件可取,所以正向枚举即可
for(int i=1;i<=n;i++{
for(int v=w[i];v<=V;v++){
dp[v] = max(dp[v],dp[v-w[i]]+c[i]);
}
}