借着本题还玩了一晚上象棋(bushi
本蒟蒻终于(复述)了一遍佬的答案,思路是这样的
在过河卒问题里,棋盘上有一个卒和一匹马。卒只能向下或者向右移动,马会控制它所在位置以及按照 “日” 字规则能跳到的位置,卒不能经过马控制的点。
我们的目标是计算卒从棋盘左上角走到右下角有多少种不同的路径。
check
根据马走 “日” 字的规则,马控制的点满足两个条件:
一是两点横纵坐标差值的绝对值之和为 3,二是横纵坐标差值绝对值的最大值为 2。同时满足这两个条件的点就是马控制的点,返回 true
,否则返回 false
。
在过河卒问题里,设 f[i][j]
表示卒走到坐标 (i, j)
位置的路径数量,也就是说,卒只能从上方 (i - 1, j)
或者左方 (i, j - 1)
走到当前位置 (i, j)
,那么到达 (i, j)
的路径数量就是到达上方点和左方点的路径数量之和。状态转移方程为
f[i][j] = f[i - 1][j] + f[i][j - 1]
(前提是该点没有被马控制)。
终点坐标和马的坐标,然后都加 2 是为了避免边界处理的麻烦,相当于给棋盘的最上面和最左边空出两行两列。
假设我们不把坐标加上 2,直接从 (0, 0)
开始计算。当卒在棋盘的最左边一列(即 j = 0
)时,在计算 f[i][0]
时,根据状态转移方程需要用到 f[i][0 - 1]
,也就是 f[i][-1]
,这就出现了数组越界的情况,因为数组下标不能为负数。
如果坐标只加 1,在处理棋盘最左上角的实际起点位置(加 1 后变为 (1, 1)
)时,还是会面临边界问题。例如,当计算 f[1][1]
时,根据状态转移方程需要用到 f[1 - 1][1]
(即 f[0][1]
)和 f[1][1 - 1]
(即 f[1][0]
),这样就出现了数组越界的情况,因为数组下标为 0 可能不在我们有效的棋盘范围内。
这个地方我觉得语言描述很难看懂,也可能是本人语言能力和理解力不行,其实写一遍两层for循环就知道咋回事了,外循环是行,内循环是列,二维数组转化为一维数组:f [ j ]表示到 j 列的路径数,每增加一行,更新一遍f [ j ],即
f [ j ] = f [ j ] + f [ j - 1 ]
滚动数组其实就是复用
这里举例子说明。
例如从(0,0)走到(2,2)(坐标加2后,即从(2,2)走到(4,4) )
初始f [ 2 ]=1
计算第 2 行(i = 2
),f [ 2 ]=1, f [ 3 ]=1, f [ 4 ]=1
计算第 3 行(i = 3
),f [ 2 ]=1, f [ 3 ]=2, f [ 4 ]=3
计算第 4 行(i = 4
) f [ 2 ]=1, f [ 3 ]=3, f [ 4 ]=6
#include
using namespace std;
#define ll long long
int dx, dy, mx, my;
ll f[25];
bool check(int x, int y){
if (x==mx && y==my) return 1;
return (abs(x-mx)+abs(y-my)==3 && max(abs(x-mx), abs(y-my))==2);
}
int main(){
cin >> dx >>dy >>mx >>my;
dx += 2;
dy += 2;
mx += 2;
my += 2;
f[2]=1;
for (int i=2; i<=dx; i++){
for (int j=2; j<=dy; j++){
if (check(i, j)) {
f[j]=0;
continue; //直接跳到for循环的下一次迭代
}
f[j] += f[j-1];
}
}
cout << f[dy] <
B3637
思路很简单,两层for循环,但本蒟蒻还是看的题解才写出来...
#include
using namespace std;
int main(){
int a[5010], n;
cin >> n;
for (int i=1; i<=n; i++){
cin >> a[i];
}
vector f(n+1, 1);
for (int i=1; i<=n; i++){
for (int j=1; j<=i; j++){
if (a[j]result) result = f[i];
}
cout << result <
本题仍然参考大佬的题解
#include
using namespace std;
int n, a[200010], b[200010];
//b[i]表示循环到i时,第i个数所在的有效序列的和
int main(){
cin >> n;
for (int i=1; i<=n; i++){
cin >> a[i];
}
for (int i=1; i<=n; i++){
if (b[i-1]+a[i]>a[i]) b[i]=b[i-1]+a[i];
else b[i]=a[i];
}
int result =b[1];
for (int i=1; i<=n; i++){
if (b[i]>result) result = b[i];
}
cout << result<< endl;
return 0;
}
大佬还对空间复杂度进行了优化,直接O(1)了,鼠鼠拜服,学习一下这种写法。
就是把a数组和b数组都优化成1个变量,并且是在求数组b[ ] 的过程中找最值。
#include
using namespace std;
int n, a, b, ans=-10010;
int main(){
cin >> n;
for (int i=1; i<=n; i++){
cin >> a;
if (i==1) b=a;
else b = max(a, a+b);
ans = max(ans, b);
}
cout << ans<< endl;
return 0;
}
P3399
写到本题的时候本蒟蒻还没悟怎么用动态规划思想解题,然后又又又看题解里大佬说推状态转移方程,动态规划思想的核心难道是推推状态转移方程(?,2025年AI已经发展到如此规划,本蒟蒻仍然学习不好好利用有效工具,罪过,下面是豆包老师的总结:
动态规划(Dynamic Programming,简称 DP)是一种通过把原问题分解为相对简单的子问题,并保存子问题的解来避免重复计算,从而解决复杂问题的算法策略。下面详细解释动态规划的思想、状态转移方程以及如何刷题做到举一反三。
动态规划主要基于两个核心特性:最优子结构和重叠子问题。!!!
一个问题的最优解可以由其子问题的最优解组合而成。也就是说,大问题的最优解包含了小问题的最优解。例如在过河卒问题中,到达某个点的最优路径数量(这里路径数量的最优就是最多路径)可以由到达其上方点和左方点的路径数量推导出来,即 f[i][j] = f[i - 1][j] + f[i][j - 1]
,这体现了最优子结构的特性。
在求解问题的过程中,很多子问题会被重复计算。动态规划通过保存这些子问题的解(通常使用数组),避免了重复计算,从而提高了算法的效率。比如在斐波那契数列问题中,计算 F(n)
时会多次用到 F(n - 1)
和 F(n - 2)
,使用动态规划可以把已经计算过的 F(k)
的值保存起来,下次再需要时直接使用,而不是重新计算。
状态转移方程是动态规划的关键,它描述了如何从子问题的解推导出原问题的解。推导状态转移方程一般可以按以下步骤进行:
明确问题的状态表示,也就是用什么来描述子问题。例如在背包问题中,状态可以定义为 dp[i][j]
,表示前 i
个物品放入容量为 j
的背包中所能获得的最大价值。
考虑状态之间的转移关系,即如何从已知的状态推导出未知的状态。这需要分析问题的规则和约束条件。比如在过河卒问题中,由于卒只能向右或向下走,所以到达 (i, j)
点的路径数量等于到达 (i - 1, j)
点和 (i, j - 1)
点的路径数量之和,得到状态转移方程 f[i][j] = f[i - 1][j] + f[i][j - 1]
。
确定状态转移的边界情况,也就是最简单的子问题的解。例如在过河卒问题中,起点的路径数量为 1,即 f[2][2] = 1
;在斐波那契数列问题中,F(0) = 0
,F(1) = 1
。
111111111111111111111
下面回到本题
显然,状态转移方程:
f[i][j] = min(f[i][j - 1], f[i - 1][j - 1] + D[i] * C[j])
f[i][j - 1]
的含义:它表示在第 j - 1
天已经到达了第 i
号城市,而在第 j
天选择休息,不进行移动。由于休息不会产生额外的疲劳度,所以在第 j
天到达第 i
号城市的最少疲劳度就等于第 j - 1
天到达该城市的最少疲劳度。f[i - 1][j - 1] + D[i] * C[j]
的含义:f[i - 1][j - 1]
表示在第 j - 1
天到达了第 i - 1
号城市的最少疲劳度。D[i]
是第 i - 1
号城市到第 i
号城市的距离,C[j]
是第 j
天的气候恶劣值,那么 D[i] * C[j]
就是在第 j
天从第 i - 1
号城市移动到第 i
号城市所产生的疲劳度。所以 f[i - 1][j - 1] + D[i] * C[j]
表示在第 j
天从第 i - 1
号城市移动到第 i
号城市的总疲劳度。#include
using namespace std;
int n, m, d[1010], c[1010], f[1010][1010];
const int INF=2139063143;
int main(){
memset(f,0x7f,sizeof(f));
cin >> n>> m;
for (int i=1; i<=n; i++){
cin >> d[i];
}
for (int i=1; i<=m; i++){
cin >> c[i];
}
for (int i=0;i<=m;i++) f[0][i]=0; //第i天在第0城市
for (int i=1; i<=n; i++){
for (int j=i; j<=m-(n-i); j++){
f[i][j] = min (f[i][j-1], f[i-1][j-1]+d[i]*c[j]);
}
}
int ans=f[n][n];
for (int i=n; i<=m; i++){
if (f[n][i]