Class - DP 1

入门:The Triangle // oj 1163

①经典递归:显然会超时 (O(2^n)),但已经清晰地表示了思路

#include<iostream>
using namespace std;
int N, a[102][102];
int f(int i,int j){ //从(i,j)走到最底端的最长路径 
	if(i==N) return a[i][j];
	return max(f(i+1,j),f(i+1,j+1))+a[i][j]; 
} 
int main(){
	scanf("%d",&N);
	for(int i=1;i<=N;++i) for(int j=1;j<=i;++j) scanf("%d",a[i]+j);
	cout<<f(1,1)<<endl;
	return 0;
}

②经典动规:将 f(i,j) 的结果保存为数组,空间换时间,O(n^2)

#include<iostream>
using namespace std;
int N, a[102][102];
int DP[102][102];
int f(int i,int j){ 
	if(DP[i][j]!=-1) return DP[i][j]; //避免了大量冗余 
	if(i==N) DP[i][j]=a[i][j];
	else {
		int t1=f(i+1,j), t2=f(i+1,j+1);
		DP[i][j] = max(t1,t2) + a[i][j]; 
	}
	return DP[i][j];
} 

int main(){
	scanf("%d",&N);
	for(int i=1;i<=N;++i) for(int j=1;j<=i;++j) {
		scanf("%d",a[i]+j);
		DP[i][j]=-1;
	}
	cout<<f(1,1)<<endl;
	return 0;
}

③改进:自下而上计算,下面的每一行都完全为上面一层服务,郭神称之为“人人为我”

#include<iostream>
using namespace std;
int N, a[102][102];
int DP[102][102];
int main(){
	scanf("%d",&N);
	for(int i=1;i<=N;++i) for(int j=1;j<=i;++j) scanf("%d",a[i]+j);
	for(int j=1;j<=N;++j) DP[N][j]=a[N][j];
	for(int i=N-1;i>0;--i) 
	for(int j=1;j<=i;++j){
		DP[i][j]=max(DP[i+1][j],DP[i+1][j+1])+a[i][j];
	}
	cout<<DP[1][1]<<endl;
	return 0;
}

④再改进:DP 数组变成一维的就行(郭神甚至直接把它搞成了指针),不过时间依旧,只是为了省点空间

#include<iostream>
using namespace std;
int N, a[102][102];
int *DP;
int main(){
	scanf("%d",&N);
	for(int i=1;i<=N;++i) for(int j=1;j<=i;++j) scanf("%d",a[i]+j);
	DP=a[N];
	for(int i=N-1;i>0;--i)
	for(int j=1;j<=i;++j){
		DP[j]=max(DP[j],DP[j+1])+a[i][j];
	}
	cout<<DP[1]<<endl;
	return 0;
}


入门:最长上升子序列 // oj 2757

相对简单的动规,只要找到合适的状态就可以写出来。O(N^2)

①“人人为我”

#include<iostream>
#include<algorithm>
using namespace std;
//第一反应是求前 i 个数的最大上升子序列,但不满足无后效性
//所以变成求以 a[i] 结尾的最大上升子序列 
int N, a[1002];
int dp[1002]={};
int main(){
	scanf("%d",&N);
	for(int i=0;i<N;++i) {
		scanf("%d",a+i);
		dp[i]=1; //带来了好几次WA,要意识到最小的长度是 1
	}
	for(int i=1;i<N;++i)
		for(int j=0;j<i;++j){
			if(a[j]<a[i])
				dp[i]=max(dp[i],dp[j]+1);
		}
	cout<<*max_element(dp,dp+N)<<endl; 
	return 0;
}

②“我为人人”

#include<iostream>
#include<algorithm>
using namespace std;
int N, a[1002];
int dp[1002]={};
int main(){
	scanf("%d",&N);
	for(int i=0;i<N;++i) {
		scanf("%d",a+i);
		dp[i]=1;
	}
	for(int i=0;i<N;++i)
		for(int j=i+1;j<N;++j){ //这里是不同之处,看当前的 i 可以为后面做些什么
			if(a[j]>a[i])
				dp[j]=max(dp[j],dp[i]+1);
		}
	cout<<*max_element(dp,dp+N)<<endl; 
	return 0;
}

入门:最长公共子序列 // oj 1458

#include<iostream>
#include<cstring>
#include<string>
using namespace std;
string a,b;
int dp[1002][1002]; //a[0]-a[i]和b[0]-b[j]的最大公共子序列长度
int main(){
	while(cin>>a>>b){
		memset(dp,0,sizeof(dp));
		for(int i=0;i<a.size();++i)
			for(int j=0;j<b.size();++j){
				if(a[i]==b[j]) {
					if(i==0||j==0) dp[i][j]=1; 
					else dp[i][j]=dp[i-1][j-1]+1;
				} 
				else {
					if(i&&j) dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
					else if(i==0&&j) dp[i][j]=dp[i][j-1];
					else if(i&&j==0) dp[i][j]=dp[i-1][j];
					else dp[i][j]=0;
				}
			}
		cout<<dp[a.size()-1][b.size()-1]<<endl;
	}
	return 0;
}

小成:帮助 Jimmy // oj 1661

思路:设Jimmy当前位于 k 板上,只要求出它从左走、从右走的最短时间,取Min即可
leftTime := 下面的第一块板子 t, 若高度差 >MAX ,time=IFINITE;
   否则,time = 高度差 + min(l+x-t.x1,r+t.x2-x);

l=lefttime(t); r=righttime(t);

另外,写这个的时候突然发现,“人人为我”递推简直好用到爆!

①“人人为我”类递推

#include<iostream>
#include<algorithm>
using namespace std;
#define INFINITE (1<<30)
int N,X,Y,MAX;
struct plat{
	int h;
	int x1;
	int x2;
	bool operator<(const plat&p){
		return h>p.h;  
	}
}P[1002];
int leftTime[1002],rightTime[1002];

int main(){
	int t; scanf("%d",&t);
	while(t--){
		scanf("%d%d%d%d",&N,&X,&Y,&MAX);
		for(int i=1;i<=N;++i) scanf("%d%d%d",&P[i].x1,&P[i].x2,&P[i].h);
		P[0].x1=X,P[0].x2=X,P[0].h=Y; //始位置看作0号板
		sort(P,P+N+1); //从高到低排序
		P[N+1].x1=-20000,P[N+1].x2=20000,P[N+1].h=0; //地面
		for(int i=N;i>=0;--i){
			int j,dh;
			for(j=i+1;j<=N;++j){
				if(P[j].x1<=P[i].x1&&P[j].x2>=P[i].x1) break;
			}
			dh=P[i].h-P[j].h;	
			if(dh>MAX) leftTime[i]=INFINITE;
			else {
				if(j==N+1) leftTime[i]=dh;
				else leftTime[i]=dh+min(leftTime[j]+P[i].x1-P[j].x1,rightTime[j]+P[j].x2-P[i].x1);
			}
			for(j=i+1;j<=N;++j){
				if(P[j].x1<=P[i].x2&&P[j].x2>=P[i].x2) break;
			}
			dh=P[i].h-P[j].h;
			if(dh>MAX) rightTime[i]=INFINITE;
			else {
				if(j==N+1) rightTime[i]=dh;
				else rightTime[i]=dh+min(leftTime[j]+P[i].x2-P[j].x1,rightTime[j]+P[j].x2-P[i].x2);
			}			
		}
		printf("%d\n",min(leftTime[0],rightTime[0]));
	}
}

②记忆型递归:本质上是把递推的调用数组的部分改成了调用函数,以及各种return

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
#define INFINITE (1<<30)
int N,X,Y,MAX;
struct plat{
	int h;
	int x1;
	int x2;
	bool operator<(const plat&p){
		return h>p.h;  
	}
}P[1002];
int leftTime[1002],rightTime[1002];

int minTime(int i,bool l_r){ //从第i往下走的最短时间,l_r区分向哪边走 
	if(i==N+1) return 0;
	int x;
	if(l_r) x=P[i].x1;
	else x=P[i].x2;
	int j=i+1;
	for(;j<=N;++j){
		if(x>=P[j].x1&&x<=P[j].x2) break;
	}
	int dh=P[i].h-P[j].h;
	if(dh>MAX) return INFINITE;
	if(j==N+1) return dh;
	int lefttime=dh+x-P[j].x1;
	int righttime=dh+P[j].x2-x;
	if(leftTime[j]==-1) leftTime[j]=minTime(j,1);
	if(rightTime[j]==-1) rightTime[j]=minTime(j,0);
	lefttime+=leftTime[j];
	righttime+=rightTime[j];
	return min(lefttime,righttime);
}

int main(){
	int t; scanf("%d",&t);
	while(t--){
		scanf("%d%d%d%d",&N,&X,&Y,&MAX);
		for(int i=1;i<=N;++i) scanf("%d%d%d",&P[i].x1,&P[i].x2,&P[i].h);
		P[0].x1=X,P[0].x2=X,P[0].h=Y; //始位置看作0号板
		sort(P,P+N+1); //从高到低排序
		P[N+1].x1=-20000,P[N+1].x2=20000,P[N+1].h=0; //地面
		memset(leftTime,-1,sizeof(leftTime));
		memset(rightTime,-1,sizeof(rightTime));
		printf("%d\n",minTime(0,1));
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值