AcWing算法提高课笔记——数字三角形

本文给出了几道数字三角形模型的题解。

数字三角形模型通常给定一个矩阵,从矩阵的左上角走到右下角,每次只能向右走或者向下走,求最大值或者最小值。

这种问题可以用f[i, j]来表示从[1, 1]走到[i, j]处的Max\Min,有状态转移方程:

f[i, j] = max(f[i - 1, j], f[i, j - 1]) + a[i, j]

如果是走两遍,就用f[k, i1, i2]表示从[1, 1]走到[i1, j1]和[i2, j2]处的Max\Min,其中i1 + j1 = k = i2 + j2。状态转移方程为:

f[k, i1, i2] = max(f[k - 1, i1 - 1, i2], f[k - 1, i1, i2 - 1], f[k - 1, i1, i2], f[k - 1, i1 - 1, i2 - 1]) + a[i, j]

本题所有题目都来自AcWing算法提高课。

摘花生

1015. 摘花生 - AcWing题库

Hello Kitty想摘点花生送给她喜欢的米老鼠。

她来到一片有网格状道路的矩形花生地(如下图),从西北角进去,东南角出来。

地里每个道路的交叉点上都有种着一株花生苗,上面有若干颗花生,经过一株花生苗就能摘走该它上面所有的花生。

Hello Kitty只能向东或向南走,不能向西或向北走。

问Hello Kitty最多能够摘到多少颗花生。

在这里插入图片描述

输入格式

第一行是一个整数T,代表一共有多少组数据。

接下来是T组数据。

每组数据的第一行是两个整数,分别代表花生苗的行数R和列数 C。

每组数据的接下来R行数据,从北向南依次描述每行花生苗的情况。每行数据有C个整数,按从西向东的顺序描述了该行每株花生苗上的花生数目M。

输出格式

对每组输入数据,输出一行,内容为Hello Kitty能摘到得最多的花生颗数。

数据范围

1≤T≤100,
1≤R,C≤100,
0≤M≤1000

输入样例:
2
2 2
1 1
3 4
2 3
2 3 4
1 6 5
输出样例:
8
16

思路和代码

用f[i, j]表示从第1行第1列,到第i行第j列,路上最多能采集到的花生数量。

因为只能往下和往右走,因此f[i, j]只能从f[i - 1, j]和f[i, j - 1]转移过来。

因此,有:

f[i, j] = max(f[i - 1, j], f[i, j - 1]) + a[i, j]

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;

const int N = 110;

int t, r, c;
int a[N][N];
int f[N][N];

int main() {
    cin >> t;
    while(t -- ) {
        cin >> r >> c;
        for(int i = 1; i <= r; i ++ ) {
            for(int j = 1; j <= c; j ++ ) {
                scanf("%d", &a[i][j]);
            }
        }
        
        memset(f, 0, sizeof f);
        
        for(int i = 1; i <= r; i ++ ) {
            for(int j = 1; j <= c; j ++ ) {
                f[i][j] = max(f[i - 1][j], f[i][j - 1]) + a[i][j];
            }
        }
        
        cout << f[r][c] << endl;
        
    }
    
    return 0;
}

最低通行费

一个商人穿过一个 N×N 的正方形的网格,去参加一个非常重要的商务活动。

他要从网格的左上角进,右下角出。

每穿越中间 1 个小方格,都要花费 1 个单位时间。

商人必须在 (2N−1) 个单位时间穿越出去。

而在经过中间的每个小方格时,都需要缴纳一定的费用。

这个商人期望在规定时间内用最少费用穿越出去。

请问至少需要多少费用?

注意:不能对角穿越各个小方格(即,只能向上下左右四个方向移动且不能离开网格)。

输入格式

第一行是一个整数,表示正方形的宽度 N。

后面 N 行,每行 N 个不大于 100 的正整数,为网格上每个小方格的费用。

输出格式

输出一个整数,表示至少需要的费用。

数据范围

1≤N≤100

输入样例:
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

BASIC
输出样例:
109

TEXT
样例解释

样例中,最小值为 109=1+2+5+7+9+12+19+21+33。

思路和代码

用f[i, j]表示走到[i, j]的最小花销。“商人必须在 (2N−1) 个单位时间穿越出去。”表示只能向下或者向右走。这道题和上一道摘花生类似,不同之处是这道题求的是最小值。

状态转移方程:

f[i, j] = min(f[i - 1, j], f[i, j - 1]) + a[i, j]

初始化时,因为是求最小值,要注意边界时不能取到周围的值,所以把f初始化为0x3f。状态[1, 1]要从[0, 1]或者[1, 0]转移过来,所以把f[1, 0]或者f[0, 1]设为0.

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;

const int N = 110;

int n;
int a[N][N];
int f[N][N];

int main() {
	cin >> n;
	
	memset(f, 0x3f, sizeof f);
	
	for(int i = 1; i <= n; i ++ ) {
		for(int j = 1; j <= n; j ++ ) {
			scanf("%d", &a[i][j]);
			f[i][j] = 0;
		}
	}
	
	f[0][1] = 0; 
	
	for(int i = 1; i <= n; i ++ ) {
		for(int j = 1; j <= n; j ++ ) {
			f[i][j] = min(f[i - 1][j], f[i][j - 1]) + a[i][j];
		}
	}
	
	cout << f[n][n] << endl;
	
	return 0;
}


方格取数

[P1004 NOIP2000 提高组] 方格取数 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

题目背景

NOIP 2000 提高组 T4

题目描述

设有 N × N N \times N N×N 的方格图 ( N ≤ 9 ) (N \le 9) (N9),我们将其中的某些方格中填入正整数,而其他的方格中则放入数字 0 0 0。如下图所示(见样例):

在这里插入图片描述

某人从图的左上角的 A A A 点出发,可以向下行走,也可以向右走,直到到达右下角的 B B B 点。在走过的路上,他可以取走方格中的数(取走后的方格中将变为数字 0 0 0)。
此人从 A A A 点到 B B B 点共走两次,试找出 2 2 2 条这样的路径,使得取得的数之和为最大。

输入格式

输入的第一行为一个整数 N N N(表示 N × N N \times N N×N 的方格图),接下来的每行有三个整数,前两个表示位置,第三个数为该位置上所放的数。一行单独的 0 0 0 表示输入结束。

输出格式

只需输出一个整数,表示 2 2 2 条路径上取得的最大的和。

样例 #1

样例输入 #1

8
2 3 13
2 6  6
3 5  7
4 4 14
5 2 21
5 6  4
6 3 15
7 2 14
0 0  0

样例输出 #1

67

提示

数据范围: 1 ≤ N ≤ 9 1\le N\le 9 1N9

思路和代码

这道题和上面的题不同之处在于这道题是走两遍的,对于每一个格子,它上面的数字只能加一次。

假设同时走两条路,那么当第一条路走到的点[i1, j1]和第二条路走到的点[i2, j2]重合时,一定有i1 + j1 = i2 + j2.

我们设i1 + j1 = k = i2 + j2.那么k从2逐渐增加到2 * n.

设f[k, i1, i2]表示第一条路走到了点[i1, j1],第二条路走到了点[i2, j2],且i1 + j1 = k,f[k, i1, i2]表示取得的数之和的最大值。

f[k, i1, i2]可以从4个点转移过来:f[k - 1, i1, i2], f[k - 1, i1 - 1, i2], f[k - 1, i1, i2 - 1], f[k - 1, i1 - 1, i2 - 1]。即[i1, j1]是从[i1 - 1, j1]或者[i1, j1 - 1]转移过来的,[i2, j2]是从[i2 - 1, j2]或者[i2, j2 - 1]转移过来的,二者排列组合就得到了f[k, i1, i2]的转移方程。

f[k, i1, i2] = max(f[k - 1, i1 - 1, i2], f[k - 1, i1, i2 - 1], f[k - 1, i1, i2], f[k - 1, i1 - 1, i2 - 1]) + t。

其中t表示[i1, j1]和[i2, j2]两个点能取到的数。

#include<iostream>
#include<algorithm>
using namespace std;

const int N = 15;

int g[N][N];
int n;
int f[2 * N][N][N];

int main() {
    cin >> n;
    int a, b, c;
    while(cin >> a >> b >> c, a || b || c) {
        g[a][b] = c;
    }
    
    for(int k = 2; k <= 2 * n; k ++ ) {
        for(int i1 = 1; i1 <= n; i1 ++ ) {
            for(int i2 = 1; i2 <= n; i2 ++ ) {
                int t = g[i1][k - i1];
                if(i1 != i2) t += g[i2][k - i2];
                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);
            }
        }
    }
    
    cout << f[2 * n][n][n] << endl;
    return 0;
}

传纸条

[P1006 NOIP2008 提高组] 传纸条 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

[NOIP2008 提高组] 传纸条

题目描述

小渊和小轩是好朋友也是同班同学,他们在一起总有谈不完的话题。一次素质拓展活动中,班上同学安排坐成一个 m m m n n n 列的矩阵,而小渊和小轩被安排在矩阵对角线的两端,因此,他们就无法直接交谈了。幸运的是,他们可以通过传纸条来进行交流。纸条要经由许多同学传到对方手里,小渊坐在矩阵的左上角,坐标 ( 1 , 1 ) (1,1) (1,1),小轩坐在矩阵的右下角,坐标 ( m , n ) (m,n) (m,n)。从小渊传到小轩的纸条只可以向下或者向右传递,从小轩传给小渊的纸条只可以向上或者向左传递。

在活动进行中,小渊希望给小轩传递一张纸条,同时希望小轩给他回复。班里每个同学都可以帮他们传递,但只会帮他们一次,也就是说如果此人在小渊递给小轩纸条的时候帮忙,那么在小轩递给小渊的时候就不会再帮忙。反之亦然。

还有一件事情需要注意,全班每个同学愿意帮忙的好感度有高有低(注意:小渊和小轩的好心程度没有定义,输入时用 0 0 0 表示),可以用一个 [ 0 , 100 ] [0,100] [0,100] 内的自然数来表示,数越大表示越好心。小渊和小轩希望尽可能找好心程度高的同学来帮忙传纸条,即找到来回两条传递路径,使得这两条路径上同学的好心程度之和最大。现在,请你帮助小渊和小轩找到这样的两条路径。

输入格式

第一行有两个用空格隔开的整数 m m m n n n,表示班里有 m m m n n n 列。

接下来的 m m m 行是一个 m × n m \times n m×n 的矩阵,矩阵中第 i i i j j j 列的整数表示坐在第 i i i j j j 列的学生的好心程度。每行的 n n n 个整数之间用空格隔开。

输出格式

输出文件共一行一个整数,表示来回两条路上参与传递纸条的学生的好心程度之和的最大值。

样例 #1

样例输入 #1

3 3
0 3 9
2 8 5
5 7 0

样例输出 #1

34

提示

【数据范围】

对于 30 % 30\% 30% 的数据,满足 1 ≤ m , n ≤ 10 1 \le m,n \le 10 1m,n10
对于 100 % 100\% 100% 的数据,满足 1 ≤ m , n ≤ 50 1 \le m,n \le 50 1m,n50

【题目来源】

NOIP 2008 提高组第三题。

思路和代码

这道题和方格取数很相似,用方格取数的思路也能做。它和方格取数的不同点在于它不能经过同一个点两次。但是注意,每个点的值都是正数,它的最优解一定不可能经过一个点两次。证明链接:AcWing 275. 证明传纸条为何可以使用方格取数的代码 - AcWing

另外需要注意i1和i2的取值范围,它不能超过行数m且不能超过k。

#include<iostream>
using namespace std;

const int N = 60;

int g[N][N];
int n, m;
int f[2 * N][N][N];

int main() {
    cin >> m >> n;
    
    for(int i = 1; i <= m; i ++ ) 
        for(int j = 1; j <= n; j ++ ) 
            scanf("%d", &g[i][j]);
    
    for(int k = 2; k <= m + n; k ++ ) {
        for(int i1 = 1; i1 <= m && i1 < k; i1 ++ ) {
            for(int i2 = 1; i2 <= m && i2 < k; i2 ++ ) {
                int t = g[i1][k - i1];
                if(i1 != i2) t += g[i2][k - i2];
                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);
			}
        }
    }
    
    cout << f[n + m][m][m] << endl;
    return 0;
    
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值