Number Triangles
题目简化
观察下面的数字金字塔。
写一个程序来查找从最高点到底部任意处结束的路径,使路径经过数字的和最大。每一步可以走到左下方的点也可以到达右下方的点。
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
输入格式
第一个行一个正整数 r r r ,表示行的数目。
后面每行为这个数字金字塔特定行包含的整数。
输出格式
单独的一行,包含那个可能得到的最大的和。
提示
【数据范围】
对于
100
%
100\%
100% 的数据,
1
≤
r
≤
1000
1\le r \le 1000
1≤r≤1000,所有输入在
[
0
,
100
]
[0,100]
[0,100] 范围内。
思路
u n k n o w n p t s unknown~ pts unknown pts
没头没脑的穷举,我也不知道你能骗多少分.
55 p t s 55pts 55pts
这是一个动态决策题,每次都有两种选择,即
明显可以用回溯dfs
搜索,那么如果有一个有
n
n
n层的数字三角形,那么完整路径一共
2
n
−
1
2^n-1
2n−1条,肯定超时.
55 p t s 55pts 55pts
虽然也是55,但好像思维难度提高了
更高效的想法是dp.
不妨推样例:
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
举一个例子,我们求到7
的最大路径.
不难想到,我们算到3
和8
的最大路径.
由这个思路我们继续往下想:
7-8...
|
3-1...
|
8-7...
|
2-5?
|
4?
那到4
的最大路径呢?很简单就是4;
那么一步步往上回溯.
取到(左儿子的最大路径与到右儿子的最大路径)的最大值.
我们将坐标铺设进三角形:7(1,1),3(2,1),8(2,2),8(3,1)…
∴ d p 5 , 1 = 4 , d p 5 , 2 = 5 , d p 4 , 1 = max d p 5 , 1 , d p 5 , 2 ∴dp_{5,1}=4,dp_{5,2}=5,dp_{4,1}=\max{dp_{5,1}},{dp_{5,2}} ∴dp5,1=4,dp5,2=5,dp4,1=maxdp5,1,dp5,2
也就是说
d
p
i
,
j
=
a
i
,
j
+
max
d
p
(
i
+
1
,
j
)
,
d
p
(
i
+
1
,
j
+
1
)
dp_{i,j}=a_{i,j}+\max{dp_{(i+1,j)}},{dp_{(i+1,j+1)}}
dpi,j=ai,j+maxdp(i+1,j),dp(i+1,j+1)
这样我们就得到了
状态转移方程.
我们不难写出代码:
int solve(int i,int j){
int a1,a2;
if(i<n){
a1=solve(i+1,j),a2=solve(i+1,j+1);
return a[i][j]+max(a1,a2);
}
else return a[i][j];
}
你也看到了,竟然T了,跟回溯没区别.qwq.
time: O ( 2 n ) O(2^n) O(2n)
memory: O ( n ) O(n) O(n)
100 p t s 100pts 100pts
失败是成功之母,TLE是WA之母,WA是AC之母.
不要TwT,我们其实快A了!
我们知道递归总会溢出,那我们尽量减少动规次数.
其实这个程序有重复计算!
∴我们应该把 d p ( i , j ) dp_{(i,j)} dp(i,j)登记下来.
这叫做
记忆化搜索
#include <bits/stdc++.h>
using namespace std;
int d[1024][1024],a[1024][1024],n;
void input(){ //读入
int i,j;
for(i=1;i<=n;i++)
for(j=1;j<=i;j++)
cin>>a[i][j];
}
int dp(int i,int j){ //记忆化搜索
if(d[i][j]>=0)
return d[i][j]; //边界
return d[i][j]=a[i][j]+(i==n ? 0 :
max(dp(i+1,j),dp(i+1,j+1)));
}
int main(){
ios::sync_with_stdio(false);
memset(d,-1,sizeof(d)); //清空数组-1;
cin>>n;
input();
cout<<dp(1,1);
return 0;
}
time: O ( n 2 ) O(n^2) O(n2)
我们的问题结束了.
至于递推dp,请你自己研究,思想同理.代码如下:
做完这道题,你还可以试试: