ProblemA(HDU2084)
这题题目都说了经典DP
由上往下推,由于结果状态多,不好处理。无疑由底往上推比较方便,都归于一个起点,只需要算出由底往上得到的最大价值即可。
方程:dp[i][j] = max(dp[i+1][j],dp[i+1][j+1])+a[i][j];
#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
int a[105][105],dp[105][105];
int main()
{
int t,n,i,j;
scanf("%d",&t);
while(t--)
{
scanf("%d",&n);
memset(dp,0,sizeof(dp));
for(i = 1;i<=n;i++)
{
for(j = 1;j<=i;j++)
scanf("%d",&a[i][j]);
}
for(i = n;i>=1;i--)
{
for(j = 1;j<=i;j++)
{
dp[i][j] = max(dp[i+1][j],dp[i+1][j+1])+a[i][j];
}
}
printf("%d\n",dp[1][1]);
}
return 0;
}
ProblemB(HDU2602)
这是非常典型的01背包问题。
dp[i][v]表示前i件物品恰放入一个容量为v的背包可以获得的最大价值。
状态转移方程:dp[i][v]=max{dp[i-1][v],dp[i-1][v-cost[i]]+value[i]}
这里提醒注意体积为零的情况,如:
1
5 0
2 4 1 5 1
0 0 1 0 0
结果为12
#include<iostream>
using namespace std;
int dp[1000][1000];
int max(int x,int y)
{
return x>y?x:y;
}
int main()
{
int t,n,v,i,j;
int va[1000],vo[1000];
cin>>t;
while(t--)
{
cin>>n>>v;
for(i=1;i<=n;i++)
cin>>va[i];
for(i=1;i<=n;i++)
cin>>vo[i];
memset(dp,0,sizeof(dp));//初始化操作
for(i=1;i<=n;i++)
{
for(j=0;j<=v;j++)
{
if(vo[i]<=j)//表示第i个物品将放入大小为j的背包中
dp[i][j]=max(dp[i-1][j],dp[i-1][j-vo[i]]+va[i]);//第i个物品放入后,那么前i-1个物品可能会放入也可能因为剩余空间不够无法放入
else //第i个物品无法放入
dp[i][j]=dp[i-1][j];
}
}
cout<<dp[n][v]<<endl;
}
return 0;
}
当然01背包是可以优化成一维的。
dp[i][v]是由dp[i-1][v]和dp[i-1][v-c[i]]两个子问题递推而来,完全可以对i降维。但一定要注意,必须在每次主循环中以v=V..0的顺序推dp[v],这样才能保证推dp[v]时dp[v-c[i]]保存的是状态dp[i-1][v-c[i]]的值,而没有被覆盖。了解过背包系列算法的应该知道,如果倒过来(0..V)会对每个物品产生叠加,造成重复放置,这正好是解决完全背包(物品数量无限)的算法。
#include<iostream>
using namespace std;
#define Size 1111
int va[Size],vo[Size];
int dp[Size];
int Max(int x,int y)
{
return x>y?x:y;
}
int main()
{
int t,n,v;
int i,j;
cin>>t;
while(t--)
{
cin>>n>>v;
for(i=1;i<=n;i++)
cin>>va[i];
for(i=1;i<=n;i++)
cin>>vo[i];
memset(dp,0,sizeof(dp));
for(i=1;i<=n;i++)
{
for(j=v;j>=vo[i];j--)
{
dp[j]=Max(dp[j],dp[j-vo[i]]+va[i]);
}
}
cout<<dp[v]<<endl;
}
return 0;
}
ProblemC(HDU1003)
很简单的一维DP,状态转移方程为f[i]=max(f[i-1]+a[i],a[i])
最后扫一遍找最优解。
由于需要位置,很明显最后扫一遍找最优解的时候,找到的那位即为末位置,那么只要在递推的时候顺便记录一下起始位置即可:若a[i]>f[i-1]+a[i],说明要更新,pos[i]=i;否则起始位置不变直接向后加,pos[i]=pos[i-1]。
#include <iostream>
#include <stdio.h>
#include <algorithm>
using namespace std;
int f[100010],a[100010],pos[100010];
int main()
{
int T,t,n;
scanf("%d",&T);t=0;
while(T--)
{
scanf("%d",&n);
for(int i=0;i<n;i++)
scanf("%d",&a[i]);
f[0]=a[0];pos[0]=0;
for(int i=1;i<n;i++)
if(f[i-1]+a[i]>=a[i])
{
f[i]=f[i-1]+a[i];
pos[i]=pos[i-1];
}
else
{
f[i]=a[i];
pos[i]=i;
}
int j=0;
for(int i=1;i<n;i++)
if(f[i]>f[j]) j=i;
t++;printf("Case %d:\n",t);
printf("%d %d %d\n",f[j],pos[j]+1,j+1);
if(T>0) printf("\n");
}
return 0;
}
当然这个题其实可以一边做一边找最大值,那么再简化一下其实可以不用数组记录,就变成了用几个变量遍历过去保存最优解的方法。这是由DP进一步抽象过来的,但由于看起来已经不像DP的模式了,这里就不写这种方法咯~
ProblemD(POJ2533)
又是一种非常经典的DP类型,很多题目都可以抽象到这个原型——最长上升子序列。当前点上升序列的长度,可由其前面比它值小的点加一得到,那么最大的长度即比较前面所有能与它连起来的情况。
转移方程:f[i]=max{f[j]}+1; ( 0 <= j < i && a[j] < a[i] )
初值f[i]=1。
#include <iostream>
#include <stdio.h>
#include <algorithm>
using namespace std;
int f[100010],a[100010];
int main()
{
int n;
scanf("%d",&n);
for(int i=0;i<n;i++)
{
scanf("%d",&a[i]);
f[i]=1;
}
for(int i=1;i<n;i++)
for(int j=0;j<i;j++)
if(a[j]<a[i] && f[j]+1>f[i])
f[i]=f[j]+1;
int ans=0;
for(int i=0;i<n;i++)
if(f[i]>ans) ans=f[i];
printf("%d\n",ans);
return 0;
}