dp例题

1.第一题(迷宫权值)

在这里插入图片描述

数据规模和约定
  对于30%的评测用例,1 <= n, m <= 10;
  对于50%的评测用例,1 <= n, m <= 20;
  对于所有评测用例,1 <= n <= 100,-10000 <= 权值 <= 10000。
输出格式
  输出一个整数,表示最大权值和。
样例输入

3 5
-4 -5 -10 -3 1
7 5 -9 3 -10
10 -2 6 -10 -4

样例输出:15

思路:dp问题,反转思路,对于一个点的权值 = 其左/上方能到达的点的最大权值 + 自己的权值,依次求出每个点最大的权值即可

/* 状态转移方程
	x = 1 且 y = 1		f = a[x][y]
	other				f = a[x][y] + max{w[x][y-123]、w[x-1][y-012]、w[x-2][y-01]、w[x-3][y]}
										减123的意思是y-1、y-2、y-3
*/
#include<iostream>
#include<algorithm>

using namespace std;

int  a[101][101], w[101][101], r, c;
const int INF = 0xc0c0c0c0; // 负无穷

int dp(int x, int y){
	if (x == 1 && y == 1) return a[1][1];
	int m = INF;
	// max{w[x][y-123]}
	for (int i = 1; i <= 3; i++){
		if ((y - i) > 0) m = max(m, w[x][y - i]);
	}
	// max{w[x-1][y-012]}
	for (int i = 0; i <= 2; i++){
		if ((y - i) > 0 && (x - 1) > 0) m = max(m, w[x - 1][y - i]);
	}
	// max{w[x-2][y-01]}
	for (int i = 0; i <= 1; i++){
		if ((y - i) > 0 && (x - 2) > 0) m = max(m, w[x - 2][y - i]);
	}
	// max{m, w[x-3][y]}
	if ((x - 3) > 0) m = max(m, w[x - 3][y]);

	return a[x][y] + m;
}

int main(){
	cin >> r >> c;
	for (int i = 1; i <= r; i++){
		for (int j = 1; j <= c; j++){
			cin >> a[i][j];
		}
	}
	for (int i = 1; i <= r; i++){
		for (int j = 1; j <= c; j++){
			w[i][j] = dp(i, j);
		}
	}
	/* 输出每个位置的最大权重
	for (int i = 1; i <= r; i++){
		for (int j = 1; j <= c; j++){
			cout << w[i][j] << " ";
		}
		cout << endl;
	}
	*/
	cout << w[r][c] << endl;
	return 0;
}

当时没想到dp,想到的是dfs,示例是对的,但应该比较耗时,代码如下

#include<iostream>
#include<stack>
#include<cmath>
using namespace std;

int n, m;
int maze[102][102] = {0};
int dic[9][2]={{0,1},{0,2},{0,3},{1,0},{1,1},{1,2},{2,0},{2,1},{3,0}};
long long ans;
struct Point{
	int x, y;
	long long value;
	Point(int a, int b, long long c):x(a), y(b), value(c){}
};
stack<Point> st;

bool in(int i, int j){
	return (i >= 1 && j >= 1 && i <= n && j <= m);
}

void dfs(){
	while(!st.empty()){
		Point tp = st.top();
		st.pop();
		if(tp.x == n && tp.y == m){
			ans = max(ans, tp.value);
			continue;
		}
		
		for(int i = 0; i < 9; i++){
			int dx = tp.x + dic[i][0];
			int dy = tp.y + dic[i][1];
			if(in(dx, dy)){
				Point a(dx, dy, tp.value + maze[dx][dy]);
				st.push(a);
			}
		}
	}
}

int main()
{
	cin >> n >> m;
	for(int i = 1; i <= n; i++){
		for(int j = 1; j <= m; j++){
			cin >> maze[i][j];
		}
	} 
	Point p(1,1,maze[1][1]);
	st.push(p);
	dfs();
	cout << ans;
	return 0;
}

参考:https://blog.csdn.net/weixin_43521592/article/details/112489745

2.第二题

第十一届蓝桥杯C++
画中漂流
【问题描述】
在梦境中,你踏上了一只木筏,在江上漂流。根据对当地的了解,你知道在你下游 D 米处有一个峡谷,如果你向下游前
进大于等于 D 米则必死无疑。现在你打响了急救电话,T 秒后救援队会到达并将你救上岸。水流速度是1 m/s,你现在有 M 点体力。每消耗一点体力,你可以划一秒桨使船向上游前进 1m,否则会向下游前进 1m (水流)。M 点体力需在救援队赶来前花光。因为江面太宽了,凭借你自己的力量不可能上岸。请问,有多少种划桨的方案可以让你得救。

两个划桨方案不同是指:存在某一秒钟,一个方案划桨,另一个方案不划。

【输入格式】
输入一行包含三个整数 D, T, M。

【输出格式】
输出一个整数,表示可以让你得救的总方案数,答案可能很大,请输出方
案数除以 1, 000, 000, 007 的余数
【样例输入】
1 6 3
【样例输出】
5
【评测用例规模与约定】
对于 50% 的评测用例,1 ≤ T ≤ 350。
对于所有评测用例,1 ≤ T ≤ 3000, 1 ≤ D ≤ T, 1 ≤ M ≤ 1500。

思路
我们可以发现,每i秒之间的状态是固定的(在哪个可达位置)所以这些可达状态必然是从上一层的合法方案的i+1秒或i-1秒到达的所以可以用一个dp.

f[i][j] 表示在第i秒在位置k的方案数
f[i][j] = f[i - 1][j - 1] + f[i - 1][j + 1]
每层计算需要卡i的区间即可

代码

#include<iostream>

using namespace std;
const int N = 3010,M = 60010,K = 3000,mod = 1e9+7;
int f[N][M];

int main(){
    int D,n,m;
    scanf("%d%d%d",&D,&n,&m);
    f[0][K] = 1;
    for(int i = 1;i <= n;i++){
        // 可以看做每分钟下游一次,如果上划则滑2次
        // 向上游最多m次 当前最多i次   下滑最多i次且至少要保证大于D  
        for(int j = K + i - min(2 * i , 2 *  m);j <=min(K + i,K + D - 1);j++){
            f[i][j] = (0ll + f[i - 1][j - 1] + f[i - 1][j + 1])%mod;
        }
    }
    long long ans = 0;
    // 统计所有的可能
    for(int j = K + n - min(2 * n,2 *  m);j <=min(K + n,K + D - 1);j++){
        ans = (ans + f[n][j])% mod;
    }
    cout << ans <<endl;
    return 0;
}

3.

在这里插入图片描述
样例输入

6
1 3 2 3 4 5
1
2

样例输出

6
1

数据规模和约定
  对于40%的数据,n<=50;
  对于100%的数据,n<=2000,0<=wi<=109。
思路:
找递推公式:
dp[i][k]表示以第i个数为结尾的,递增k个数的序列个数
最外层k是控制问题规模的(k从2到4)(先求小规模问题,再求大规模问题)
所以找dp[i][k]
当a[i]>a[j]时,dp[i][k]+=dp[j][k-1];

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int n;
ll dp[2001][5];//dp[i][j]表示以i为结尾的递增j个数的序列个数
ll a[2001];

int main()
{
    memset(dp,0,sizeof dp);
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%lld",&a[i]);
    }

    for(int i=1;i<=n;i++){
        dp[i][1]=1;
    }

    for(int k=2;k<=4;k++){//k表示连续k个数递增
        for(int i=2;i<=n;i++){//i为终止边界
            for(int j=1;j<i;j++){//j为从最左到最右边找
                if(a[i]>a[j]){
                    dp[i][k]+=dp[j][k-1];
                }
            }
        }
    }
    ll ans=0;
    for(int i=4;i<=n;i++){
        ans+=dp[i][4];
    }
    printf("%lld",ans);
    //注意输出以每个为结尾连续递增4个数的和,而不是只输出dp[n][4]
    return 0;
}

参考:https://blog.csdn.net/timelessx_x/article/details/115568745

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值