目录
在算法竞赛中,有时候会遇到一些图形相关的题目,需要运用图论相关的知识进行求解。今天我们将一起探讨一个比较常见的模型——数字三角形模型。
在数字三角形模型中,每个位置的值是唯一的,而且从前一个位置到当前位置只能沿着三角形的一条边走。以一个数字三角形的顶端开始,可以通过向下或向右移动一步来达到下一个位置,依此类推。
以下是一个数字三角形模型的例子:
1 23 345 4567 56789
在这个例子中,从顶端开始,分别可以到达以下位置:
现在,假设有一个数字三角形,每个位置的值都是非负整数,并且每个位置的值不能超过该位置的行数。我们需要找出从顶端到最后一行的一个路径,使得路径上所有位置的总和最大。这个问题可以通过动态规划(DP)的方法来解决。
以下是一个使用动态规划解决该问题的C++代码示例:
- #include
- using namespace std;
- const int MAXN = 100005;
- int dp[MAXN], pre[MAXN];
- int main() {
- int n;
- scanf("%d", &n);
- for (int i = 1; i <= n; i++) {
- for (int j = i; j >= 1; j--) {
- scanf("%d", &dp[j]);
- pre[j] = j - 1;
- }
- }
- int ans = dp[1];
- for (int i = 2; i <= n; i++) {
- int mn = INT_MAX;
- for (int j = i; j >= 1; j--) {
- mn = min(mn, dp[j]);
- dp[j] += mn * (i - pre[j] - 1);
- }
- pre[i] = i - 1;
- ans = max(ans, dp[i]);
- }
- printf("%d\n", ans);
- return 0;
- }
在这个代码中,我们首先读入数字三角形的每一行的整数,并将它们存储在数组dp中,同时建立一个pre数组来存储每个位置的最前面一个位置。然后,我们通过两次循环遍历整个数字三角形:第一次循环中,我们通过遍历每个位置的最前面一个位置,找到从该位置开始的最大和路径;第二次循环中,我们通过遍历每个位置的最前面一个位置,计算从该位置开始的最大和路径,并更新答案。最后,我们输出答案。
需要注意的是,以上代码仅适用于非负整数的情况。如果数字三角形中的值可能为负数,那么需要做一些修改。例如,可以在dp数组中存储每个位置的值和该位置的最前面一个位置的差的绝对值,然后在第二次循环中计算最大和路径时加上这个值。
给出了一个数字三角形,从三角形的顶部到底部有多条不同的路径。对于每条路径,把路径上面的数加起来可以得到一个和,累加和最大的路径称为“最佳路径”。题目的任务就是求出最佳路径上的数字之和。
注意:路径上的每一步只能从一个数走到下一层和它最近的左边的数或者右边的数。
- #include
- #include
- using namespace std;
-
- #define MAX_NUM 100
- int d[MAX_NUM+10][MAX_NUM+10];
- int N;
-
- int MaxSum(int r,int j) //递归求解最大路径和
- {
- if(r == N)
- return d[r][j];
- int sum1 = MaxSum(r+1,j);
- int sum2 = MaxSum(r+1,j+1);
- if(sum1 > sum2)
- return sum1 + d[r][j];
- return sum2 + d[r][j];
- }
-
- int main()
- {
- int m;
- cin>>N;
- for(int i = 1;i <= N; i++)
- for(int j = 1;j <= i; j++)
- cin >> d[i][j];
- cout << MaxSum(1,1);
- return 0;
- }
5 7 3 8 8 1 0 2 7 4 4 4 5 2 6 5
Hello Kitty想摘点花生送给她喜欢的米老鼠。
她来到一片有网格状道路的矩形花生地(如下图),从西北角进去,东南角出来。
地里每个道路的交叉点上都有种着一株花生苗,上面有若干颗花生,经过一株花生苗就能摘走该它上面所有的花生。
Hello Kitty只能向东或向南走,不能向西或向北走。
问Hello Kitty最多能够摘到多少颗花生。
传统的思考方式:阶段、决策、最优子结构、无后效性。
从集合角度来思考DP问题——闫氏思考法
动态规划:
状态表示f[i, j]:
a) 集合 所有从(1,1)走到(i,j)的路线的最大值
b) 属性:Max/Min/数值
状态计算 - 对应的是集合的划分
很重要的集合划分依据:“最后”,最后一步很关键,像本题目中的从上面下来,还是从左边过来。它的划分原则有不重复和不漏,当然在求Max和Min的时候不一定满足不重复原则。
- #include
- #include
-
- using namespace std;
-
- const int N = 110;
-
- int n, m;
- int w[N][N];
- int f[N][N];
-
- int main()
- {
- int T;
- scanf("%d", &T);
- while(T--)
- {
- scanf("%d%d",&n, &m);
- for(int i = 1;i <= n; i++)
- {
- for(int j = 1;j <= m; j++)
- {
- scanf("%d", &w[i][j]);
- }
- }
-
- for(int i = 1;i <= n; i++)
- {
- for(int j = 1;j <= m; j++)
- {
- f[i][j] = max(f[i-1][j], f[i][j-1]) + w[i][j];
- }
- }
- printf("%d\n", f[n][m]);
- }
- return 0;
- }
一个商人穿过一个N×N的正方形的网格,去参加一个非常重要的商务活动。他要从网格的左上角进,右下角出。每穿越中间1个小方格,都要花费1个单位时间。商人必须在(2N-1)个单位时间穿越出去。而在经过中间的每个小方格时,都需要缴纳一定的费用。这个商人期望在规定时间内用最少费用穿越出去。请问至少需要多少费用?
注意:不能对角穿越各个小方格(即,只能向上下左右四个方向移动且不能离开网格)。
【输入样例] 5 1 4 6 8 10 2 5 7 15 17 6 8 9 18 20 10 11 12 19 21 20 23 25 29 33 【输出样例】 109
- #include
- #include
-
- using namespace std;
-
- const int N = 110, INF = 1e9;
-
- int m;
- int w[N][N];
- int f[N][N];
-
- int main()
- {
- scanf("%d", &m);
- for(int i = 1;i <= m; i++)
- {
- for(int j = 1;j <= m; j++)
- {
- scanf("%d", &w[i][j]);
- }
- }
-
- for(int i = 1;i <= m; i++)
- {
- for(int j = 1;j <= m; j++)
- {
- if(i == 1 && j == 1) f[i][j] = w[i][j];
- else
- {
- f[i][j] = INF;
- if(i > 1) f[i][j] = min(f[i][j], f[i-1][j]) + w[i][j];
- if(j > 1) f[i][j] = min(f[i][j], f[i][j-1]) + w[i][j];
- }
- }
- }
- printf("%d\n",f[m][m]);
- return 0;
- }
题目描述 设有N×N的方格图,我们在其中的某些方格中填入正整数,而其它的方格中则放入数字0。如下图所示:
某人从图中的左上角A出发,可以向下行走,也可以向右行走,直到到达右下角的B点。在走过的路上,他可以取走方格中的数(取走后的方格中将变为数字0)。
此人从A点到B点共走了两次,试找出两条这样的路径,使得取得的数字和为最大。
f[i] [j]表示所有从[1] [1]走到[i] [j]的路径最大值
f[i] [j] = max(f[i-1] [j], f[i] [j-1]) + w[i] [j];
走两次:
f[i1] [j1]和f[i2] [j2]表示所有从[1] [1],[1] [1]走到[i1] [j1],[i2] [j2]的路径最大值
f[i] [j] = max(f[i-1] [j], f[i] [j-1]) + w[i] [j];
处理“同一个格子不能被重复选择”
只有在i1+j1 == i2+j2,那么两条路径的格子才可能会重合
f[k, i1, i2]表示从所有[1] [1],[1] [1]分别走到(i1, k-i1)和(i2, k-i2)的路径的最大值
k表示两条路线当前走到的格子的横纵坐标之和。
- #include
- #include
-
- using namespace std;
-
- const int N = 15;
-
- int n;
- int w[N][N];
- int f[N-2][N][N];
-
- int w[N][N];
- int f[N*2][N][N];
-
- int main()
- {
- scanf("%d", &n);
-
- int a, b, c;
- while(cin >> a >> b >> c, a || b || c)
- w[a][b] = c;
-
- for(int k = 2;k <= n+n; k++)
- {
- for(int i1 = 1;i1 <= n; i1++)
- {
- for(int i2 = 1;i2 <= n; i2++)
- {
- int j1 = k - i1, j2 = k - i2;
- if(j1 >= 1 && j1 <= n && j2 >= 1 && j2 <= n)
- {
- int t = w[i1][j1];
- if(i1 != i2) t += w[i2][j2];
- int &x = f[k][i1][j1];
- x = max(x, f[k-1][i1-1][i2-1] + t);
- x = max(x, f[k-1][i1-1][i2] + t);
- x = max(x, f[k-1][i1][i2-1] + t);
- x = max(x, f[k-1][i1][i2] + t);
- }
- }
- }
- }
- printf("%d\n",f[n+n][n][n]);
- return 0;
- }
复写一遍:
- #include
- #include
-
- using namespace std;
-
- const int N = 15;
- int n;
- int w[N][N];
- int f[N*2][N][N];
-
- int main()
- {
- scanf("%d", &n);
- int a, b, c;
- while(cin >> a >> b >> c, a || b || c)
- w[a][b] = c;
-
- for(int k = 1;k <= 2*n; k++)
- {
- for(int i1 = 1;i1 <= n; i1++)
- {
- for(int i2 = 1;i2 <= n; i2++)
- {
- int j1 = k - i1, j2 = k - i2;
- if(j1 >= 1 && j1 <= n && j2 >= 1 && j2 <= n)
- {
- int t = w[i][j];
- if(i1 != i2) t += w[i2][j2];
- int &x = f[k][i1][i2];
- x = max(x, f[k-1][i1-1][i2-1] + t);
- x = max(x, f[k-1][i1-1][i2] + t);
- x = max(x, f[k-1][i1][i2-1] + t);
- x = max(x, f[k-1][i1][i2] + t);
- }
- }
- }
- }
- printf("%d", f[n+n][n][n]);
- return 0;
- }