蓝桥杯 数字三角形【第十一届】【省赛】【C组】C++ DP 动态规划 DFS 深搜

该博客讨论了一个使用动态规划解决的问题,即在给定的数字三角形中找到从顶部到底部的最大路径和。博主解释了如何设置状态转移方程,并通过奇偶性优化求解,还提到了深搜的解决方案,但指出深搜容易导致超时。最后提供了AC的DP代码实现。
摘要由CSDN通过智能技术生成

资源限制

时间限制:1.0s 内存限制:256.0MB

问题描述

上图给出了一个数字三角形。从三角形的顶部到底部有很多条不同的路径。对于每条路径,把路径上面的数加起来可以得到一个和,你的任务就是找到最大的和。

路径上的每一步只能从一个数走到下一层和它最近的左边的那个数或者右边的那个数。此外,向左下走的次数与向右下走的次数相差不能超过 1。

输入格式

输入的第一行包含一个整数N (1<N≤100),表示三角形的行数。下面的 N 行给出数字三角形。数字三角形上的数都是 0 至 100 之间的整数。

输出格式

输出一个整数,表示答案。

样例输入

5
7
3 8
8 1 0 
2 7 4 4
4 5 2 6 5

Data

样例输出

27

思路简介

又是dp...,感觉蓝桥杯不如改名dp杯得了(bushi)...

不是说蓝桥的题都是dp题,而是很多题都能用动态规划的方法,甚至可以说动态规划是算法界的半壁江山,所以学好动态规划非常重要,遇到一些题要习惯尝试dp。话不多说,开始解析此题:

首先看图以及题,题干中所说的左下和右下,是对于图而言的。所以当我们用二位数组来存数据的时候,应该对应的是下方和右下。那么怎么用动态规划来解决此题呢?

不妨先设f[i][j]表示i点到达j点的最大权值之和,那么怎么算呢?那就考虑对于每个i,j点,是怎么到达的。

既然我们每次都能往下和右下走,即对于每个i j点能走到i+1,ji+1,j+1点。所以每个i j点应该是由i-1,ji-1,j-1点走来的。

所以便找到了递推式,即所谓状态方程:f[i][j]=max(f[i-1][j]+a[i][j],f[i-1][j-1]+a[i][j])。

但是同样值得注意的是,题目中要求向下的次数和向右下的次数只差不能超过1。先以样例为例吧,n为5,走到底层需要4步,其实显然这四步一定是走了俩次向下两次向右下,因为往下走并不改变方向,所以相当于走了四次向下,两次向右,所以最终是走到了底层中间的位置。偶数同理,例如n为4,走到底层需要 3步,那么可以是两次向下一次右下,或一次向下两次右下,相当于走了三次下,一次右或者两次右,所以当是偶数时,最大值便取决于最底层中间的两个数的较大值。其实推广到n为任何的奇数或者偶数,此结论显然都是成立的,所以只要判断n的奇偶再输出便可。

 

if (n & 1)//如果是奇数的话
        printf("%d", f[n][n / 2 + 1]);
    else
        printf("%d", max(f[n][n / 2 + 1], f[n][n / 2]));

 那么n&1是什么意思?下面简单介绍一下:

显然整数不仅包括0和正整数,而且也包括负整数,这种方式如果是根据取余结果来判断是否为0来判断偶数、偶数的话无论正负显然都成立,但是如果用1来判定是否是奇数在某些情况下就欠妥了(因为负奇数对2取余结果为-1)。其实将整数用二进制表示后,可以很方便的进行奇偶性的判断,因为偶数,无论是正整数还是负整数,二进制表示时其最低位为0奇数,无论是正整数还是负整数,二进制表示时其最低位都为1,利用这一特性,可以非常方便快速的完成判断。

n&1,即和1进行&操作,显然1的二进制除了第一位是1,其它位都0,如果是奇数的话,第一位是1。和1进行&后,只有1&1为1,其他结果都是0,所以显然奇数n&1后结果为1.同理,偶数n&1为0,从而快速精准判断n的奇偶性。

AC代码如下

#include<bits/stdc++.h>
using namespace std;
const int N = 110;
int a[N][N], f[N][N], n;
int main()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= i; j++)
            scanf("%d", &a[i][j]);
    f[1][1] = a[1][1];//初始化
    for (int i = 2; i <= n; i++)//从第二行开始往下更新
        for (int j = 1; j <= i; j++)//对于每一行每一位都是如此更新权值
         f[i][j] = max(f[i - 1][j] + a[i][j], f[i - 1][j - 1] + a[i][j]);
    if (n & 1)//如果是奇数的话
        printf("%d", f[n][n / 2 + 1]);
    else
        printf("%d", max(f[n][n / 2 + 1], f[n][n / 2]));
    return 0;
}

其实这题也可以尝试深搜,但是显然很容易TLE,可以通过一些优化来省去不必要的搜索,如当

abs(R-L)-(n-x)>1,n-x即剩下还要走几步,R-L的差的绝对值如果比n-x还要大1,那么剩下的几步全走某一个方向都没法弥补,没法使得R和L的绝对值只差<=1了,那么就不用搜索。还可以通过奇偶性优化等等。以下列出AC了一半的dfs代码,我就懒得优化了...(谁让dp这么好用)

#include<bits/stdc++.h>
using namespace std;
int wk[2][2] = { {1,1}, {1,0} };//向右下走和向下
int n, a[110][110];
int vis[110][110], maxn;
inline int read(){
	char ch=getchar();int x=0,f=1;
	while(ch<'0'||ch>'9'){
		if(ch=='-')f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9'){
		x=x*10+ch-'0';
		ch=getchar();
	}return x*f;
}
void dfs(int x, int y, int sum, int R, int L) {//现在的位置是x,y,总权值是sum 往右走了R步,往左走了L步
	if (x == n){//到达最底层
		if(abs(R-L)<=1)//说明结果合法 
		maxn = max(maxn, sum);//更新最大值
		return;
	}
	for (int i = 0; i < 2; i++) {//枚举两个个方向 
		int tx = x + wk[i][0], ty = y + wk[i][1];//即将到达的坐
			if(n-x-abs(R-L)<-1)continue;
		if (tx<1 || ty>n || ty<1 || ty>n || vis[tx][ty])continue;//边界条件
		vis[tx][ty] = 1;//标记说明访问过
		switch (i) {
		case 0: {
			dfs(tx, ty, sum + a[tx][ty], R+1, L );//向右下走
			break;
		}
		case 1: {
			dfs(tx, ty, sum + a[tx][ty], R , L+1);//向下走 相当于图中的向左下走 
			break;
		}
		}
		vis[tx][ty] = 0;//取消标记
	}
}
int main(){
	scanf("%d", &n);
	for (int i = 1; i <= n; i++) 
		for (int j = 1; j <= i; j++)
			a[i][j]=read(); 
	vis[1][1]=1;
	dfs(1,1,a[1][1],0,0);
	printf("%d", maxn);
	return 0;
}

评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Prudento

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值