dp 之 记忆化递归

记忆化递归

一、滑雪

传送门

#include<iostream>
#include<algorithm>
using namespace std;
const int N=305;
int h[N][N]; 
int f[N][N];//从i,j出发的最长路径
int dx[4]={0,0,1,-1};
int dy[4]={1,-1,0,0};
int m,n;
bool inside(int x,int y){
	return x>=1&&x<=m&&y>=1&&y<=n;
}
int solve(int x,int y){
	if(f[x][y]!=-1)return f[x][y];
	f[x][y]=1;
	for(int i=0;i<4;i++){
		int cx=x+dx[i];
		int cy=y+dy[i];
		if(inside(cx,cy)&&h[x][y]>h[cx][cy]){
//			f[x][y]+=solve(cx,cy);
//只能从一个方向开始一条路径,并不是把各个方向走的路程相加 
			f[x][y]=max(f[x][y],solve(cx,cy)+1);
		} 
	}
	return f[x][y];
}
int main(){

	cin>>m>>n;
	for(int i=1;i<=m;i++){
		for(int j=1;j<=n;j++){
			cin>>h[i][j];
		} 
	}
	fill(f[0],f[0]+N*N,-1);
	int maxlen=0;
	for(int i=1;i<=m;i++){
		for(int j=1;j<=n;j++){
			int len=solve(i,j);
			maxlen=max(maxlen,len);
		} 
	}
	cout<<maxlen;
    return 0;
}

二、乘积最大

传送门
设有一个长度为N的数字串,要求选手使用K个乘号将它分成K+1个部分,找出一种分法,使得这K+1个部分的乘积能够为最大。

1、 动态规划递推

一般这种只有一种运算符的就考虑dp[i][j]表示前i个数中插入j个乘号的大小 dp[n][k],可以由前i个数中插入j-1个乘号的 dp[n][k-1]推来(直接计算掉最后一个乘号)
dp[i][j]=max(dp[i][j],dp[1][j-1]*a[2][i],dp[2][j-1]*a[3][i],dp[3][j-1]*a[4][i]……);
当然了,这里求一串数任意区间形成的数值也是用个二维数组存放,通过递推得到的a[i][j]=a[j][j]+10*a[i][j-1];

#include<iostream>
#include<string.h>
using namespace std;
const int N=45;
typedef long long ll;
ll a[N][N];
ll dp[N][N];//dp[i][j]表示前i个数中插入j个乘号的大小 dp[n][k]

int main(){
	ll n,k;
	cin>>n>>k;//n位数中插入k个乘号 
//	string str;
//	cin>>str;
	char str[N];
	cin>>str+1;
//	ll s;
//	cin>>s;
	for(int i=n;i>=1;i--){
		a[i][i]=str[i]-'0';
//	a[i][i]=s%10;
//	s/=10;
	}
	for(int i=1;i<=n;i++){//12345
		for(int j=i;j<=n;j++){
			a[i][j]=a[j][j]+10*a[i][j-1];
		}
	}
	for(int i=1;i<=n;i++){
		dp[i][0]=a[1][i];
	}
	for(int i=1;i<=n;i++){
		for(int j=0;j<=k;j++){
			for(int l=j;l<i;l++){//前l个数里面插j-1个乘号 
				dp[i][j]=max(dp[i][j],dp[l][j-1]*a[l+1][i]); 
		}
			}
			
	}
//	for(int j=1;j<=k;j++){
//		for(int i=j+1;i<=n;i++){
//			for(int l=1;l<=i;l++){//前l个数里面插j-1个乘号 
//				dp[i][j]=max(dp[i][j],dp[l][j-1]*a[l+1][i]); 
//		}
//			}	
//	}
	cout<<dp[n][k];
    return 0;
}

2、记忆化递归

#include<iostream>
#include<algorithm>
using namespace std;
const int N=45;
typedef long long ll;
ll a[N][N];//依旧存放每个区间段构成的数值 
ll f[N][N];
ll solve(int n,int k){
	if(f[n][k]!=-1)return f[n][k];
	if(k==0)return a[1][n];//哇塞,记住递归出口呀!!!!!! 
//	f[n][k]=max(f[n][k],f[1][k-1]*a[2][n],f[2][k-1]*a[3][n]……)
	for(int i=k;i<=n-1;i++){
		f[n][k]=max(f[n][k],solve(i,k-1)*a[i+1][n]) ;
//	本来是要用到f[i][k-1]的,但由于是记忆化递归得到,得递归调用 
	}
	return f[n][k];
}
int main(){
	ll n,k;
	cin>>n>>k;//n位数中插入k个乘号 
	char str[N];
	cin>>str+1;
	for(int i=0;i<=n;i++){
		a[i][i]=str[i]-'0';
		
	} 
	for(int i=1;i<=n;i++){
		for(int j=i;j<=n;j++){
			a[i][j]=a[i][j-1]*10+a[j][j];
		}	
	} 
	fill(f[0],f[0]+N*N,-1);
	cout<<solve(n,k);//求的f[n][k] 
    return 0;
}

三、plus subtract multiply(同样是插入运算符)

传送门
在这里插入图片描述

思路

在这里插入图片描述

#include<iostream>
#include<algorithm>
using namespace std;
const int N=2e5+5;
const int mod=1e9+7;
typedef long long int ll;
ll a[N];
ll mult[N];//前i个数连乘的积
ll mi[N];//3的i次幂 
int main(){
//由于在n个数之间插入+-*三个运算符,可以发现+、-可以相互抵消
//在某个位置i,一定有对应的分别取+和-得到的式子
//即 可以枚举+、-成对出现的位置的两个式子来进行相加,该位置后的所有数
//只要相同位置取相同符号就可以抵消,只有该位置前的连乘式对总和有贡献
//而后面这些符号的组合方式的种数就是枚举前i位连乘时前缀积的贡献次数 
	int n;
	cin>>n;
	mult[0]=mi[0]=1; 
	for(int i=1;i<=n;i++){
		cin>>a[i];
		mult[i]=mult[i-1]*a[i]%mod;
		mi[i]=mi[i-1]*3%mod;
	}
	ll res=0; 
	for(int i=1;i<n;i++){//假设前i个数连乘 
		(res+=mult[i]*2*mi[n-i-1])%=mod;
//前i个数对应的前缀积对总和的贡献次数(所有组合里包含这个位置这个前缀积的种数
//是自+/-号以后的式子的任意组合的方式种数,i到i+1之间的符号有2种(+或-)
//之后的n-i-1个间隔的运算符,+-*任意都可 2*3^(n-i-1) 
	} 
//	若n个数全部连乘
	(res+=mult[n])%=mod;
	cout<<res; 
    return 0;
}

四、记忆化递归求SG函数

#include <set> 
const int N=105;
const int M=10005;
int k;
int s[N];//选取数的集合 
int n,v;
int f[M];//记忆化递归,记录每个有向图当下局面的SG值
//f存储的是所有可能出现过的局面的sg值,
//因为每个局面的SG值都要由她所有后继局面的SG值决定 
int SG(int x){
	if(f[x]!=-1)return f[x];
//每个局面的sg值都是确定的,如果存储过了,直接返回即可
	set<int> S;
//只针对当下一个局面,存放这个局面的若干后继局面
//每个局面都会新定义一个set集合 
	for(int i=0;i<k;i++){//枚举每一个可能的后继局面,把后继局面的SG值放到x对应的set集合中 
		int ss=s[i];
		if(x>=ss){
			S.insert(SG(x-ss));
//先延伸到终点的sg值后,再回溯得出所有数的sg值 
//记忆化递归,先求出终点sg值,再从后往前
		}
	}
	for(int i=0;;i++){//选没有出现在 x的后继局面的SG值中的 最小自然数 
		if(S.count(i)==0){
			f[x]=i;
			return f[x];
		}
	} 
} 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值