dp题解

一维的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]的最大值就是答案。

**

  1. 最长上升子序列

两种求LIS的方法

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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值