DP就是和列表差不多,把先前的值求出,可以由先前值推出当前值,一直推到求出答案。更准确来说是记忆化搜索。DP要划分阶段,划分后写动态转移方程。DP一般要满足两个特性:最优性与无后效性。最优性即要满足最优解性质,保证每一步都是最优的。无后效性要满足求解某个阶段只会与上一个阶段有关,或者说一个阶段求完后,不会对前面造成影响.DP的程序一般不长,但思考比较难。
下面是例题
1.51nod 1055
题面:N个不同的正整数,找出由这些数组成的最长的等差列。
样例:http://www.51nod.com/Challenge/Problem.html#!#problemId=1055
用f[i][j]表示数列第1项为a[i],第二项为a[j]的长度,当第3项为a[k]那么f[i][j]=f[j][k]+1。且a[j]-a[i]=a[k]-a[j],变形为a[j]*2=a[i]+a[k]。因此只要枚举j,i向前扫,k向后扫可以做到O(n^2);
#include<bits/stdc++.h>
using namespace std;
int n,i,k,ans=0;
int a[11000];
short int f[11000][11000];
int main()
{
cin>>n;
for(int j=1;j<=n;j++)
cin>>a[j];
sort(a+1,a+1+n);//题目并未告知是有序数列
for(i=1;i<=n;i++)
for(int j=1;j<=n;j++)
f[i][j]=2;
for(int j=2;j<n;j++)//枚举第二项
{
i=j-1; k=j+1;
while(i>=0 && k<=n)
{
if(a[j]*2>a[i]+a[k])//第三项太小
k++;
else
{
if(a[j]*2<a[i]+a[k])//第一项太大
i--;
else
{
f[j][k]=f[i][j]+1;
if(ans<f[j][k])//找最大
ans=f[j][k];
i--; k++;
}
}
}
}
cout<<ans<<endl;
return 0;
}
2.51nod 1052 最大M子段和
题面:N个整数组成的序列a[1],a[2],a[3],…,a[n],将这N个数划分为互不相交的M个子段,并且这M个子段的和是最大的。如果M >= N个数中正数的个数,那么输出所有正数的和。
样例:http://www.51nod.com/Challenge/Problem.html#!#problemId=1052
f[i][j]表示前j项所构成i子段的最大和(必须包含着第j项),有两种情况 1、f[ i ][ j ] = f[ i ] [ j-1 ] + a[ j ] ,即把第j项融合到第j-1项的子段中,子段数没变 2、dp[ i ][ j ] = max(dp[ i-1 ] [ t ]) + a[ j ],(i-1<= t < j ) 把第 j 项作为单独的一个子段,然后找一下i-1个子段时,最大的和,然后加上a[ j ]。可以将t优化掉,t为上一行j以前的最大值。因此每做一次更新一下最大值。
#include<bits/stdc++.h>
using namespace std;
long long f[5][5100],a[5100],l=0,ans=0,n,m;
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
cin>>a[i];
memset(f,0,sizeof(f));
for(int i=1;i<=m;i++)
{
l=f[(i-1)%2][i-1];//付初始值//空间小要开滚动数组:[……%2]
for(int j=i;j<=n;j++)
{
/* l=f[i-1][i-1];
for(int k=i;k<j;k++)
l=max(l,f[i-1][k]);*/ // /* */内为t未优化程序
l=max(l,f[(i-1)%2][j-1]);
f[i%2][j]=max(f[i%2][j-1]+a[j],l+a[j]);
if(ans<f[i%2][j])
ans=f[i%2][j];
}
}
cout<<ans<<endl;
return 0;
}