DP-线性DP

DP-线性DP

线性DP简介

线性DP主要是一维思想将问题DP优化(了解DP问题)。

线性DP分类

  1. 路径问题
    1. 路径最优解
    2. 路径计数
  2. 硬币问题
  3. 序列问题
    1. 前缀和
    2. 部分和
    3. 最大和
    4. 最长 上升/不上升/下降/不下降 子序列 (注意上升和下降不代表 “ = ” )
    5. 最大 上升/不上升/下降/不下降 子序列和
    6. 最长公共子序列 (学习中……)
    7. 最长公共 上升/不上升/下降/不下降 子序列 (不太会)
    8. 特定要求下计数问题
    9. 特定要求下最优解问题
  4. 分苹果问题
  5. 整除问题 (不太会)

线性DP讲解

路径问题

路径问题可谓是DP问题中的水题(easy problem),主要是给一个矩阵图,求矩 阵图一点到另一点的最优解(看题目)或是方法数。

路径最优解问题
题目链接: 路径最优解问题
解题思路:
  1. 题目分析:首先,商人要在 ( 2n-1 ) 单位时间走到右下角,可以判断出每一步向下走或向右走;其次,用手算模拟后,发现不能考虑局部最优解,要全局化,必须用DP。
  2. DP模板
    1. 状态定义: 用f[i][j] 表示从左上角到第i行第j列时,最小花费是多少。
    2. 状态转移方程: 因为每次只能向右或向下走到一格,对于每格我们只用考虑 这个格子上面和左面格子取最小值再加上这个格子的花费,才是最优解。
    3. 状态初始化: 初始将f数组的所有值赋值成无穷大。把f[1][1]赋值为a[1][1]
代码实现:
#include<bits/stdc++.h>
using namespace std;

const int maxn=100+10;
int n;
int a[maxn][maxn],f[maxn][maxn];
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++)scanf("%d",&a[i][j]);
	}
	//初始化 
	f[1][1]=a[1][1];
	for(int i=2;i<=n;i++)f[i][1]=f[i-1][1]+a[i][1];
	for(int i=2;i<=n;i++)f[1][i]=f[1][i-1]+a[1][i];
	//状态转移方程
	for(int i=2;i<=n;i++){
		for(int j=2;j<=n;j++){
			if(f[i-1][j]<f[i][j-1])f[i][j]=f[i-1][j]+a[i][j];
			else f[i][j]=f[i][j-1]+a[i][j];
		}
	}
	printf("%d\n",f[n][n]);
	return 0;
} 
路径计数问题
题目链接:路径计数问题
解题思路
  1. 题目分析:这道题数据范围比较低用DFS即可做(不会TLE),但最好的方法用DP就可以。
  2. DP模板:
    1. 状态定义:f[i][j]表示从左下角到第i行第j列的路径数。
    2. 状态转移方程: 和上一道题差不多,就是将这个格子的左边格子的方法数和上面格子的方法数相加。
    3. 状态初始化:所有格子一开始都赋值为0,之后将f[i][1] || 1<=i<=n的方法数f[1][i] || 1<=i<=m 赋值为1。
代码实现:
#include<bits/stdc++.h>
using namespace std;

const int maxn=20+10;
int n,m;
int f[maxn][maxn];
int main(){
	scanf("%d%d",&n,&m);
	//初始化
	for(int i=1;i<=n;i++)f[i][1]=1;
	for(int i=1;i<=m;i++)f[1][i]=1;
	//状态转移方程
	for(int i=2;i<=n;i++){
		for(int j=1;j<=m;j++)f[i][j]=f[i-1][j]+f[i][j-1];
	}
	printf("%d\n",f[n][m]);
	return 0;
} 

硬币问题

硬币问题是给一定数值的物品来凑出一个更高的数值。

题目链接:硬币问题
解题思路
  1. 题目分析:此题可以发现不能保证砝码重量的差距足够大,就违反了DP无后效性的原则。so what ? DP is a good choice .
  2. DP模板:
    1. 状态定义: f[i]表示要用砝码凑重量i最少需要的砝码数。
    2. 状态转移方程:将所有砝码扫描一遍,决定可用不可用,可用打擂台法
    3. 状态初始化:凑重量0和1(特判)
代码实现:
#include<bits/stdc++.h>
using namespace std;

const int maxn=100+10;
const int maxm=1e4+10;
const int inf=1e9;
int n,m;
int a[maxn],f[maxm];
int main(){
	scanf("%d%d",&m,&n);
	for(int i=1;i<=n;i++)scanf("%d",&a[i]);
	sort(a+1,a+n+1);
	if(a[1]==1)f[1]=1;
	else f[1]=inf;
	for(int i=2;i<=m;i++){
		f[i]=inf;
		for(int j=1;j<=n&&a[j]<=i;j++){
			if(f[i]>f[i-a[j]]+1)f[i]=f[i-a[j]]+1;
		}
	}	
	if(f[m]==inf)printf("-1\n");
	else printf("%d\n",f[m]);
	return 0;
} 

序列问题

在一个一维的数据结构里进行DP优化。

前缀和问题
题目链接: 前缀和问题
解题思路
  1. 题目分析 :本题时间复杂度较低,纯属凑数淼题,但是数据范围极大,就得保存答案了,也算DP。
  2. DP模板:
    1. 状态定义:f[i]表示前i个数的前缀和。
    2. 状态转移方程:对于f[i]我们只需要把f[i-1]的值调用再加上a[i]的值就是f[i]的值。
    3. 状态初始化: f[1]=a[1]。
代码实现:
#include<bits/stdc++.h>
using namespace std;

const int maxn=1e5+10;
int n,m,k;
int a[maxn],f[maxn];
int main(){
	scanf("%d%d",&n,&m);
	scanf("%d",&a[1]);
	f[1]=a[1];
	for(int i=2;i<=n;i++){
		scanf("%d",&a[i]);
		f[i]=f[i-1]+a[i];
	}
	for(int i=1;i<=m;i++){
		scanf("%d",&k);
		printf("%d\n",f[k]);
	}
	return 0;
}
部分和问题
题目链接:部分和问题
解题思路
  1. 题目分析:本题和前缀和问题基本相似。后面DP模板细分析---->---->
  2. DP模板 (和前缀和问题same):
    1. 状态定义:f[i]表示前i个数的前缀和。
    2. 状态转移方程:对于f[i]我们只需要把f[i-1]的值调用再加上a[i]的值就是f[i]的值。
    3. 状态初始化: f[1]=a[1]。
  3. 题目变形: 如果求i->j的区间和,我们可以将j的前缀和-(i-1)的前缀和。
代码实现:
#include<bits/stdc++.h>
using namespace std;

const int maxn=1e5+10;
int n,m,x,y;
int a[maxn],f[maxn];
int main(){
	scanf("%d%d",&n,&m);
	scanf("%d",&a[1]);
	f[1]=a[1];
	for(int i=2;i<=n;i++){
		scanf("%d",&a[i]);
		f[i]=f[i-1]+a[i];
	}
	for(int i=1;i<=m;i++){
		scanf("%d%d",&x,&y);
		printf("%d\n",f[y]-f[x-1]);
	}
	return 0;
}
最大和问题
题目链接:最大和问题
解题思路
  1. 题目分析:此题最大答案肯定是所有数值和,呵呵,你先读题,有负数。此题如果纯模拟,时间复杂度为O(2^n)不用想,爆超特超TLE,DP安排。
  2. DP模板:
    1. 状态定义:f[i]表示以a[i]结尾的最大和。
    2. 状态转移方程:首先我们要把以i结尾数列的最大值算出来,再用这些值进行比较求解。
    3. 状态初始化:全为0。
代码实现:
#include<bits/stdc++.h>
using namespace std;

const int maxn=1e5+10;
const int inf=1e9;
int n,ans;
int a[maxn],g[maxn],f[maxn];
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++)scanf("%d",&a[i]);
	for(int i=1;i<=n;i++)g[i]=max(a[i],a[i]+g[i-1]);
	for(int i=1;i<=n;i++)f[i]=max(f[i-1],g[i]);
	ans=-inf;
	for(int i=1;i<=n;i++)ans=max(ans,f[i]);
	printf("%d\n",ans);
	return 0;
}
最长 上升/不上升/下降/不下降 子序列问题

题目写的小标题有点长,你们肯定想呕吐,我也是——呕呕呕呕呕!

题目链接:最长 上升/不上升/下降/不下降 子序列问题
解题思路:
  1. 题目分析:此题枚举不用想时间复杂度O(2^n*n)。把DP叫来。
  2. DP模板:
    1. 状态定义: f[i]表示以第i个元素为结尾的最长上升子序列元素个数。
    2. 状态转移方程:将f[i]之前的f[1]~f[i-1]之间的元素遍历一遍,比a[i]小且所在f数组值尽量大。
    3. 状态初始化:f[1]=1。
代码实现:
#include<bits/stdc++.h>
using namespace std;

const int maxn=1e3+10;
int n,ans;
int a[maxn],f[maxn];
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++)scanf("%d",&a[i]);
	f[1]=1,ans=1;
	for(int i=2;i<=n;i++){
		int s=0;
		for(int j=1;j<i;j++){
			if(a[j]<a[i])s=max(s,f[j]);
		}
		f[i]=s+1;
		ans=max(ans,f[i]);
	}
	printf("%d\n",ans);
	return 0;
} 
最大 上升/不上升/下降/不下降 子序列和问题

和上一题一样——呕呕呕呕呕——

题目链接: 最大 上升/不上升/下降/不下降 子序列和问题
解题思路:
  1. 题目分析:同上一题。
  2. DP模板:
    1. 状态定义:f[i]表示以元素i结尾的最长上升子序列的和。
    2. 状态转移方程: 将f[i]之前的f[1]~f[i-1]之间的元素遍历一遍,比a[i]小且所在f数组值尽量大在加上a[i]就是f[i]。
    3. 状态初始化:f[1]=a[1]。
代码实现:
#include<bits/stdc++.h>
using namespace std;

const int maxn=1e3+10;
int n,ans;
int a[maxn],f[maxn];
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++)scanf("%d,&a[i]);
	f[1]=a[1],ans=f[1];
	for(int i=2;i<=n;i++){
		int s=0;
		for(int j=1;j<i;j++){
			if(a[j]<a[i])s=max(s,f[j]);
		}
		f[i]=s+a[i];
		ans=max(ans,f[i]);
	}
	printf("%d\n",ans); 
	return 0;
} 
特定要求下计数问题
题目链接:特定要求下计数问题
解题思路:
  1. 题目分析:此题若要枚举,时间复杂度是O(2^n)。此题非DP莫属了。
  2. DP模板
    1. 状态定义:f[i][j]表示第i个坑还可已在连续j个放的方法数。
    2. 状态转移方程:需要考虑放进去或不放进去两种情况相加求解。
    3. 状态初始化:f[0][0]=0; f[0][i]=1 || 1<=i<=m ; f[i][0]=0 ||1<=i<=n
代码实现:
#include<bits/stdc++.h>
using namespace std;
#define ll long long 

const int maxn=50+10;
int n,m;
ll f[maxn][maxn];
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++)f[0][i]=1;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++)f[i][j]=f[i-1][m]+f[i-1][j-1];
	}
	printf("%lld\n",f[n][m]);
	return 0;
} 
特定要求下最优解问题
题目链接:特定要求下最优解问题
解题思路
  1. 题目注意:遇到多组数据的题,一定一定要记得数据结构初始化,this is imortant (经验教训,失分过)
  2. 题目分析:此题数据范围极大(多组数据),枚据TLE,DP来了。
  3. DP模板:
    1. 状态定义:f[i][j]表示第i家还可已在连续j个偷的方法数。
    2. 状态转移方程:需要考虑放进去或不放进去两种情况取较大值求解。
    3. 状态初始化:f[i][0]=-inf || 0<=i<=n f[0][i]=0 || 1<=i<=2
代码实现
#include<bits/stdc++.h>
using namespace std;
#define ll long long 

const int maxn=1e5+10;
const int inf=1e9;
int n,t;
int a[maxn];
ll f[maxn][5];
int main(){
	scanf("%d",&t);
	while(t--){
		scanf("%d",&n);
		f[0][0]=-inf;
		for(int i=1;i<=n;i++){
			scanf("%d",&a[i]);
			f[i][0]=-inf;
		}
		for(int i=1;i<=n;i++){
			for(int j=1;j<=2;j++)f[i][j]=max(f[i-1][2],f[i-1][j-1]+a[i]);
		}
		printf("%lld\n",f[n][2]);
	}
	return 0;
} 

分苹果问题

题目链接:分苹果问题
解题思路:
  1. 题目分析:题目还是DP题,本人,呕呕呕呕*1024^1024。
  2. DP模板:我们言归正传
    1. 状态定义: 注意这个状态定义为三维。f[i][j][k]表示i个盘子j个苹果每个盘子的苹果数不超过k个。
    2. 状态转移方程:枚举苹果数,选和不选两种情况考虑
    3. 状态初始化:f[0][0][i]=1 || 0<=i<=n,其余为0。
代码实现
#include<bits/stdc++.h>
using namespace std;

const int maxn=10+10;
int t,n,m;
int f[maxn][maxn][maxn];
int main(){
	scanf("%d",&t);
	while(t--){
		memset(f,0,sizeof(f)); 
		scanf("%d%d",&n,&m);
		//状态初始化
		for(int i=0;i<=n;i++)f[0][0][i]=1;
		for(int i=1;i<=n;i++){
			for(int j=0;j<=n;j++)f[0][i][j]=0;
		} 
		//状态转移方程
		for(int i=1;i<=m;i++){
			for(int j=0;j<=n;j++){
				for(int k=0;k<=n;k++){
					f[i][j][k]=0;
					for(int w=0;w<=min(j,k);w++)f[i][j][k]+=f[i-1][j-w][w];
				}
			}
		}
		printf("%d\n",f[m][n][n]);
	}
	return 0;
}

经过7h的选题、想思路和写代码,终于写完了。
希望大家多多评论。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_Yxz_

我只是一名ssfoier

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值