一维的dp[i]一般指 1. 处理完前i个后的最优结果 2.最后一个选择的是第i个量的最优解 3. 类似核电站问题的,求解符合条件的答案的种数:使用 所有答案-不符合的答案来计算数量。
网址: noi.openjudge.cn
1481: maximum sum(双序列最大子段和)
类似最大子段和问题(区别是这里只用加和,对于结果小于0的不用令其子段和等于0),注意使用记忆化搜索,用dp1[],dp2[]存储正序和反序的最大字段和中的动归数组,f1[],f2[]存储正序和反序的最终结果,然后遍历一边i分割数组的所有位置,求出f1[i]+f2[i+1]的最大值就是答案。
1768.最大子矩阵
类比最大子段和,该题目是将一维扩展成二维。考虑将二维的数组压缩到一维中。方法是将1,2,3…n行的数据加成一行(对应列相加,最终变成一行的,然后对一行的数据求最大连续子段和),然后求1,2,3,…n行压缩所有情况中的最大值。
#include<iostream>
#include<cstdio>
using namespace std;
#define INF 1<<20
int n;
int m[105][105];
int sumarr[10005][105]; //存O(n^2)种多行相加的结果
int dp[105];
int lss(int row){
int max=-INF;
dp[0]=sumarr[row][0];
max=dp[0];
for(int i=1;i<n;i++){
if(dp[i-1]>0) dp[i]=dp[i-1]+sumarr[row][i];
else dp[i]=sumarr[row][i];
max=(max>dp[i])?max:dp[i];
}
return max;
}
int main(){
cin>>n;
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
cin>>m[i][j];
//求出所有可能的行数相加的和 ,1行,2行.. n行
int cnt=0;
for(int rows=1;rows<=n;rows++){
for(int s=0;s<=n-rows;s++){
for(int i=s;i<=s+rows-1;i++){
for(int j=0;j<n;j++){
sumarr[cnt][j]+=m[i][j];
}
}
cnt++;
}
}
//对sumarr种每一行使用最大子段和
int max=-INF;
for(int i=0;i<cnt;i++){
int cmp=lss(i);
max=(max>cmp)?max:cmp;
}
cout<<max;
}
1775.采药
0-1背包问题(背包只能选0或1)
用dp[i,j]表示在前i个药中进行选择,且时间限制为j时的最优价格。
注意dp数组的大小,行数是药的个数m,列数是时间T的大小,两者大小不相同!!!
9267核电站(一维dp
这里求的是符合条件的次数,所以关注点在方案的数量而不是放的位置。
这里考虑的是: 符合条件的方案数= 总的方案数 - 不能放的方案数
a[i]表示前i个位置可放的方案数:
-
a[i]=2^i if i<M 当小于M时,无论怎么放都可以a
-
a[i]=2^i-1 if i==M 只有一种情况会爆炸
-
a[i]=a[i-1]+(a[i-1]-a[i-1-m]) 对于i位置,若放0,有a[i-1]种情况,若放1,则只有前m-1全是1(然后加上这个1)才会爆炸,所以要减去这种情况(注意前m-1个全是1时,则第前m位一定是0,因为要保证之前i-1位是不爆炸的,所以才有减去a[i-1-m])
注意结果用long long 来保存,因为题目中有50个位置,2^50超过了int
这里再区别一下: int最多32位(按编译器来),long 32位,long long 64位
9268酒鬼
求的是最大的酒体积总量,而像上一题求的是方案总数。
设dp[i]表示从前i个里选择出的最大体积(第i个不一定选),分两种情况,第i个选或者不选。
第i个不选时: 直接就是dp[i]=dp[i-1]
第i个选: 继续分情况:
若第i-1个选,则第i-2个一定不选 : dp[i]=dp[i-3]+m[i-1]+m[i]
若第i-1个不选,则只要保证前i-2个是最优选择 dp[i]=dp[i-2]+m[i]
90滑雪(dfs+记忆化搜索
不用bfs的原因:
bfs是层序,一般用于求最短路,而且在该问题中,遍历到一个新的点时,是上一层的点需要加一,所以用dfs更好。
注意dfs结合记忆化搜索,否则会超时!!!
dp[i,j]表示从(i,j)出发的最长滑雪路径。一开始初始化dp[i,j]=0,之后在遍历是,若dp[i,j]>0,说明这个数就是从(i,j)出发的最长滑雪路径(因为dfs是按照h递减遍历的,所以从一点出发不会遍历回来,当再次遍历到该点时,一定是从其他点遍历过来的,所以一点的值要么是0,要么就是从该点出发的最长滑雪路径),直接返回。
因为如果从某点出发不能找到下一路径,则该点的值为0(本该为1),所以最终的结果还要加上1。
747.divisibility
参考链接思路:
用dp[i][j]等于true或false表示前i个数的任意组合是否能被k除余数为j ,这样使用的就是动态规划(或者说是记忆化搜索)的思想。 要填写dp[i][j]需要遍历dp[i-1][.],通过第i-1行为true的项dp[i-1][j]以及a[i]的值推导出第i行的值。(这种做法每次都记住了上一次的所有余数的结果,更加明显的证明了动态规划就是记忆化搜索)
这里还发现一个abs的使用区别,如果使用的是中的abs函数,返回值就和参数就是int,如果使用的是中的abs函数,返回值和参数就是double类型。
另外需要注意c++中对于被除数是负数的情况和数学中的不一样,所以对于有模除运算的地方都要保证被除数变成正数再mod运算!!!
4978:宠物小精灵之收服
是一道二维背包题目,二位背包本来应该使用三维数组做,但在这里三维数组内存不够MLE,但是可以通过降维来解决,使用二维数组解决二维背包问题。这样时间复杂度不变,但是空间复杂度降低了。
注意降维后是从后往前遍历,一共需要遍历整个二维数组n次(n为精灵数量)
#include<iostream>
#include<cstdio>
using namespace std;
#define max(a,b) (a)>(b)?(a):(b)
//二维背包问题,降维后用二维数组解决 (降维后倒序遍历
struct op{
int num,cost;
};
int dp[1010][510];
int n,m,k;
op arr[110];
int main(){
cin>>n>>m>>k; //球数,体力值,精灵数
for(int i=0;i<k;i++)
cin>>arr[i].num>>arr[i].cost;
for(int i=1;i<=k;i++) //这里i=1表示从前1个精灵中选择 ,但arr[i] 从0开始,所以要减一
for(int j=n;j>=0;j--)
for(int k=m;k>=0;k--){
if(j>=arr[i-1].num&&k>=arr[i-1].cost)
dp[j][k]=max(dp[j][k],dp[j-arr[i-1].num][k-arr[i-1].cost]+1);
}
cout<<dp[n][m]<<" ";
int ans=0;
for(int i=m-1;i>=0;i--)
if(dp[n][i]==dp[n][m]) //球数相等时体力需求越小越好
ans=i;
else
break;
cout<<m-ans<<endl;
}