济南 CSP-J 刷题营 Day3 动态规划

Solution

T1 方格涂色

原题链接

4085: 方格涂色

简要思路

分列进行讨论,只有三种状态:选了上面、选了下面、上下都没选。只需讨论一下状态之间的转移即可。

完整代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MAXN=1e6+5;

int n;
long long a[5][MAXN];
long long dp[5][MAXN][5];
//dp[i][j][k] 表示当前考虑第 i 行第 j 列的格子,在 k 的情况下(k=1 代表选了,k=0 代表未选)当前数字和的最大值

signed main(){
	cin>>n;
	for(int i=1;i<=2;i++)
		for(int j=1;j<=n;j++)
			cin>>a[i][j];

	dp[2][1][1]=a[2][1];
	dp[1][1][1]=a[1][1];//每一行的第一列选中的情况先提前处理

	for(int j=2;j<=n;j++)
		for(int i=1;i<=2;i++){

			if(i==1){//第一行
				dp[i][j][0]=max(dp[i][j-1][1],dp[i+1][j-1][1]);//取上一列两个数中选中的最大值
				dp[i][j][1]=max(dp[i][j-1][0],dp[i+1][j-1][1])+a[i][j];//上一列第一行未选、上一列第二行已选
			}
			else{//第二行
				dp[i][j][0]=max(dp[i][j-1][1],dp[i-1][j-1][1]);
				dp[i][j][1]=max(dp[i][j-1][0],dp[i-1][j-1][1])+a[i][j];//dp 原理同上
			}
			//注意当第三维为 1 的时候(代表选中),勿忘加上 a[i][j]
		}

	cout<<max(dp[1][n][0],max(dp[1][n][1],max(dp[2][n][0],dp[2][n][1])))<<endl;//最后一列的两个数 选/不选 中取最大值作为答案进行输出
	return 0;
}

T2 方格行走

原题链接

4086: 方格行走

简要思路

直接在二维方格中遍历每一个可能到达的点(即保证横坐标大于等于纵坐标),用所有能够一步到达该点的(即左下、左边、下边三个位置)方案数量和最为该点的方案数。

完整代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int mod=998244353;

int n,ans;
int dp[10005][10005];//dp[i][j] 代表走到 (i,j) 这个点的方案数

signed main(){
	cin>>n;

	dp[0][0]=1;//初始值:待在起点不走的方案数

	for(int i=1;i<=n;i++)
		dp[i][0]=dp[i-1][0];//先把最下面一行的方案数赋初值

	for(int i=1;i<=n;i++)
		for(int j=1;j<=i;j++)//在循环枚举中保证合法(即横坐标 i 大于等于纵坐标 j)
			dp[i][j]=(dp[i-1][j]%mod+dp[i][j-1]%mod+dp[i-1][j-1]%mod)%mod;
			//三个可能一步到达该点的方案取和,注意及时取模

	cout<<dp[n][n]%mod<<endl;//输出能够到达 (n,n) 且合法的走法方案的数量

	return 0;
}

T3 下山路径

原题链接

4087: 下山路径

相似题目:P1434 [SHOI2002] 滑雪

简要思路

一道中规中矩的记忆化搜索板子题。

记忆化搜索
使用普通搜索时,有的时候要求的答案可能之前以前已经求过了,那么这时我们再次搜索一遍就会显得繁琐没有必要(主要是会浪费时间),所以我们运用一个数组,记录下之前搜索到的答案,如何求的答案在数组中已经有所记录,那么我们直接调用即可。

完整代码

#include<bits/stdc++.h>
#define int long long
using namespace std;

int dx[4]={0,0,1,-1};
int dy[4]={1,-1,0,0};//偏移量

int n,a[1005][1005];
int Ms[1005][1005];//Memory search:记忆化搜索

int dfs(int x,int y){
	if(Ms[x][y])return Ms[x][y];//如果之前求过就直接调用
	
	Ms[x][y]=1;//初始值为 1,因为自身就是可以经过的一个地方
	
	for(int i=0;i<4;i++){//枚举四个方向

		int xx=x+dx[i];
		int yy=y+dy[i];
		//(xx,yy) 就是枚举到的点的坐标

		if(xx>0&&yy>0&&xx<=n&&yy<=n&&a[x][y]>a[xx][yy]){
			//保证 (xx,yy) 合法:
			//1. 保证这个点存在
			//2. (xx,yy) 的高度小于 (x,y) 的高度(这样才能走到)

			dfs(xx,yy);//搜索当前位置的答案
			Ms[x][y]=max(Ms[x][y],Ms[xx][yy]+1);//维护答案,注意不要忘了 +1
		}
	}
	return Ms[x][y];
}

signed main(){
	cin>>n;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
			cin>>a[i][j];

	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++)
			cout<<dfs(i,j)<<' ';
		cout<<endl;
	}

	return 0;
}

T4 分配数字

原题链接

4088: 分配数字

简要思路

滚动数组 + 背包。

维护一个数组 dpdp[i][j] 代表前 \(i\) 个数,差值为 \(j\) 的时候,两个人的较大值最大为多少。

\(dp\) 的转移有点像背包,注意讨论大小关系转变的情况。

完整代码

#include<bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;

int dp[2][5000005];//dp[i][j] 代表前 i 个数,两个人的数字差为 j 的时候,两个人的较大值最大位多少
int a[55];
int n,ans;

signed main(){
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		ans+=a[i];//计算最大差值
	}

	int las=0,cur=1;

	for(int i=1;i<=ans;i++)
		dp[las][i]=-1e18;//初始赋值

	for(int i=1;i<=n;i++){

		for(int j=0;j<=ans;j++)
			dp[cur][j]=dp[las][j];

		for(int j=0;j<=ans;j++){//所有可能的差值

			if(j+a[i]<=ans)dp[cur][j+a[i]]=max(dp[cur][j+a[i]],dp[las][j]+a[i]);
			if(j<a[i])dp[cur][a[i]-j]=max(dp[cur][a[i]-j],dp[las][j]-j+a[i]);
			else dp[cur][j-a[i]]=max(dp[cur][j-a[i]],dp[las][j]);//考虑所有选数的可能
		}

		las^=1,cur^=1;//滚动数组
	}

	cout<<dp[las][0]<<endl;//差值为 0
	return 0;
}

讲课笔记

DP 的建模

  • 状态

  • 转移

递推类

  • Fibonacci number

  • Catalan number

  • 错排记数

线性 DP 的最优化

  • 链上最大独立集

  • LIS

补充

老师 PPT 笔记内容较少,可以当做复习的目录,这里再给大家推荐几个比较好的 DP 笔记。

This(好像是蓝书上的)

线性 DP、状压 DP、树形 DP

DP 之滚动数组的优化

posted @ 2023-08-18 15:59  CheZiHe929  阅读(21)  评论(0编辑  收藏  举报