数字三角形【汇总】

1220 数字三角形
题目链接http://codevs.cn/problem/1220/
题目描述 Description

如图所示的数字三角形,从顶部出发,在每一结点可以选择向左走或得向右走,一直走到底层,要求找出一条路径,使路径上的值最大。

输入描述 Input Description

第一行是数塔层数N(1<=N<=100)。

第二行起,按数塔图形,有一个或多个的整数,表示该层节点的值,共有N行。

输出描述 Output Description

输出最大值。

样例输入 Sample Input

5

13

11 8

12 7 26

6 14 15 8

12 7 13 24 11

样例输出 Sample Output

86

解题思路:

参考北大郭炜老师

用二维数组存放数字三角形。
D( r, j) : r行第 j 个数字(r,j1开始算)
MaxSum(r, j) : D(r,j)到底边的各条路径中,最佳路径的数字之和。
问题:求 MaxSum(1,1)
典型的递归问题。
D(r, j)出发,下一步只能走D(r+1,j)或者D(r+1, j+1)。故对于N行的三角形:
if ( r == N)  MaxSum(r,j) = D(r,j)
else  MaxSum( r, j) = Max{ MaxSum(r1,j), MaxSum(r+1,j+1) }  + D(r,j)

 

下面四段代码分别是存粹递归、记忆型递归、递推、空间优化的递推的代码:

 1 #include <stdio.h>
 2 
 3 #define maxN 101
 4 
 5 int N;
 6 int D[maxN][maxN];      //D[i][j]表示第i行第 j 个数字。其中i、j从1开始算
 7 
 8 int maxSum[maxN][maxN]; //maxSum[i][j]表示从D(i,j)到底边的各条路径中,最佳路径的数字之和。
 9 
10 
11 //代码一:纯粹递归。当N达到100是绝对是超时的。因为复杂度是O(2^N) 
12 int fun1(int i,int j)//返回从(i,j)到达最底层的最大路径之和 
13 {
14     if(i==N) return D[i][j];
15     else
16     {
17         int x=fun1(i+1,j);
18         int y=fun1(i+1,j+1);
19         return D[i][j]+(x>y?x:y);
20     }
21 }
22 
23 //代码二:记忆型递归,避免重复计算。时间复杂度O(n*n) 
24 int fun2(int i,int j)//返回从(i,j)到达最底层的最大路径之和 
25 {
26     if(maxSum[i][j]!=-1) return maxSum[i][j];
27     
28     if(i==N) maxSum[i][j]=D[i][j];
29     else
30     {
31         int x=fun2(i+1,j);
32         int y=fun2(i+1,j+1);
33         maxSum[i][j]=D[i][j]+(x>y?x:y);
34     }
35     
36     return maxSum[i][j];
37 }
38 
39 //代码三:递归变递推 
40 int fun3()
41 {
42     int i,j;
43     for(j=1;j<=N;j++) maxSum[N][j]=D[N][j];
44     
45     for(i=N-1;i>=1;i--)
46     {
47         for(j=1;j<=i;j++)
48         {
49             int max=(maxSum[i+1][j]>maxSum[i+1][j+1]?maxSum[i+1][j]:maxSum[i+1][j+1]);
50             maxSum[i][j]=D[i][j]+max;
51         }
52     }
53     return maxSum[1][1];
54 }
55 
56 //代码四:递归变递推并在空间上做优化
57 int fun4()
58 {
59     int i,j;
60 
61     for(i=N-1;i>=1;i--)
62     {
63         for(j=1;j<=i;j++)
64         {
65             int max=(D[i+1][j]>D[i+1][j+1]?D[i+1][j]:D[i+1][j+1]);
66             D[i][j]=D[i][j]+max;
67         }
68     }
69     return D[1][1];
70 }
71 
72 int main(int argc, char *argv[])
73 {
74     int i,j;
75     freopen("001.in","r",stdin);
76     scanf("%d",&N);
77     for(i=1;i<=N;i++)
78     {
79         for(j=1;j<=i;j++)
80         {
81             scanf("%d",&D[i][j]);
82             maxSum[i][j]=-1;
83         }
84     }
85     
86     //printf("%d\n",fun1(1,1));
87     //printf("%d\n",fun2(1,1));
88     //printf("%d\n",fun3());
89     printf("%d\n",fun4());
90     return 0;
91 }

 补充一个深度优先搜索的实现。仅仅只是演示一下算法,时间复杂度应该是很高,无法AC的。

 1 #include<stdio.h>
 2 #define maxN 101
 3 int n,a[maxN][maxN]={0};
 4 int xx[2]={1,1};
 5 int yy[2]={0,1};
 6 int ans=0,maxAns=0;
 7 void dfs(int x,int y)
 8 {
 9     int i;
10     for(i=0;i<2;i++)
11     {
12         int newX=x+xx[i];
13         int newY=y+yy[i];
14         if(newX<=n&&newY<=n)
15         {
16             ans=ans+a[newX][newY];
17             if(newX==n)
18             {
19                 if(ans>maxAns) maxAns=ans;
20             }
21             else dfs(newX,newY);
22             ans=ans-a[newX][newY];
23         }
24     }
25 }
26 int main()
27 {
28     int i,j;
29     freopen("data.in","r",stdin);
30     scanf("%d",&n);
31     for(i=1;i<=n;i++)
32         for(j=1;j<=i;j++) scanf("%d",&a[i][j]);
33         
34     ans=maxAns=a[1][1];
35     dfs(1,1);
36     
37     printf("%d\n",maxAns);
38     return 0;
39 }
View Code

其实本质和上面递归实现的做法是一致的,只是代码实现框架不同而已。

 

 

 

 

2193 数字三角形WW

题目链接:http://codevs.cn/problem/2193/

题目描述 Description

本道题目由上面数字三角形变化而来。具体变化是:从数字三角形顶部往底层走时必须经过某一个指定点,使之走的路程和最大

输入描述 Input Description

第1行n,表示n行
第2到n+1行为每个的权值
程序必须经过n div 2,n div 2这个点

输出描述 Output Description

最大值

样例输入 Sample Input

2
1
1 1

样例输出 Sample Output

2

数据范围及提示 Data Size & Hint

n <=25

算法分析

这道题只需让必须经过的点的权值加上一个特别大的值,最后的结果再减去这个值就行了。实际上,状态转移方程和上面第一题是没有变的。

我这里在输入时顺便把所有元素累加求和得到sum。然后把这个sum加到必须走的那个点。

 1 #include <stdio.h>
 2 #define maxN 101
 3 int n,a[maxN][maxN];
 4 int main(int argc, char *argv[])
 5 {
 6     int i,j,x,y,sum=0;
 7     scanf("%d",&n);
 8     for(i=1;i<=n;i++)
 9         for(j=1;j<=i;j++)
10             { scanf("%d",&a[i][j]); sum=sum+a[i][j]; }
11     x=n/2; y=n/2;
12     a[x][y]+=sum;
13     for(i=n-1;i>=1;i--)
14     {
15         for(j=1;j<=i;j++)
16         {
17             a[i][j]+=(a[i+1][j]>a[i+1][j+1]?a[i+1][j]:a[i+1][j+1]);
18         }
19     }
20     printf("%d\n",a[1][1]-sum);
21     return 0;
22 }

 

2198 数字三角形WWW

题目链接:http://codevs.cn/problem/2198/

题目描述 Description

本道题目由上面第一题数字三角形变化而来。具体变化是:从数字三角形必须经过某一个点,使之走的路程和最大

输入描述 Input Description

第1行n,表示n行 
第2到n+1行为每个的权值
第n+2行为两个数x,y表示必须经过的点

输出描述 Output Description

最大值

样例输入 Sample Input

2
1
1 1
1 1

样例输出 Sample Output

2

数据范围及提示 Data Size & Hint

n<=25

算法分析

这个题目和第二题并没什么区别,唯一区别就是输入时多输入x和y表示必须要走过的点。

 1 #include <stdio.h>
 2 #define maxN 101
 3 int n,a[maxN][maxN];
 4 int main(int argc, char *argv[])
 5 {
 6     int i,j,x,y,sum=0;
 7     scanf("%d",&n);
 8     for(i=1;i<=n;i++)
 9         for(j=1;j<=i;j++)
10             { scanf("%d",&a[i][j]); sum=sum+a[i][j]; }
11     //x=n/2; y=n/2;
12     scanf("%d%d",&x,&y);
13     a[x][y]+=sum;
14     for(i=n-1;i>=1;i--)
15     {
16         for(j=1;j<=i;j++)
17         {
18             a[i][j]+=(a[i+1][j]>a[i+1][j+1]?a[i+1][j]:a[i+1][j+1]);
19         }
20     }
21     printf("%d\n",a[1][1]-sum);
22     return 0;
23 }

 

上面三道题目的代码中用到的动规状态转移方程都是逆推,从最后一行往前递推。其实也是可以顺着推的。比如第三题的代码可以像下面这一段这么写:(下面代码中,f[i][j]表示的是从(i,j)这个点到达(1,1)这个点的最大路径之和。另外,这里用了两个数组,其实仅用一个数组即可,类似上述代码。)

 1 #include<iostream>
 2 #include<cstring>
 3 #include<cstdio>
 4 using namespace std;
 5 int n,x,y,w[35][35],ko;
 6 long long f[35][35];
 7 int main()
 8 {
 9     scanf("%d",&n);
10     for(int i=1;i<=n;i++)
11       for(int j=1;j<=i;j++)
12         scanf("%d",&w[i][j]);
13     scanf("%d%d",&x,&y);
14     w[x][y]+=99999999;
15     f[1][1]=w[1][1];
16     for(int i=2;i<=n;i++)
17       for(int j=1;j<=i;j++)
18         f[i][j]=w[i][j]+max(f[i-1][j],f[i-1][j-1]);
19     long long ans=0;
20     for(int i=1;i<=n;i++)
21       ans=max(ans,f[n][i]);
22     printf("%lld",ans-99999999);
23     return 0;
24 }

 

2189 数字三角形W

题目链接:http://codevs.cn/problem/2189/

这个题目仍然是由上面第一题的数字三角形变化而来。具体如下:

题目描述 Description

数字三角形
要求走到最后mod 100最大

输入描述 Input Description

第1行n,表示n行
第2到n+1行为每个的权值

输出描述 Output Description

mod 100最大值

样例输入 Sample Input

2
1
99 98

样例输出 Sample Output

99

数据范围及提示 Data Size & Hint

n<=25

算法分析

这个题目的改动使得问题处理起来比较麻烦了。本来比较大的一个数,加上那么一点点,再取模,可能就很小了。显然这已经不再满足动态规划的无后效性原则了。怎么办呢?

可以开一个布尔型的三维数组,用f[i][j][k]表示走到位置(i,j)时路径权值之和再取模能否得到k这个值,于是得到这样一个状态转移方程:

f[i][j][k]=f[i][j][k] or f[i-1][j][(k-a[i][j]+m)%m] or f[i-1][j-1][(k-a[i][j]+m)%m],这里面加上m是为了防止出现负下标。
最后找一遍那个目标状态存在就行了。

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 using namespace std;
 5 const int MAXN=26;
 6 int n;
 7 int a[MAXN][MAXN];
 8 bool f[MAXN][MAXN][101];
 9 int main()
10 {
11     scanf("%d",&n);
12     for(int i=1;i<=n;i++)
13         for(int j=1;j<=i;j++)
14             cin>>a[i][j];
15     
16     for(int i=1;i<=n;i++)f[n][i][a[n][i]%100]=1;
17     
18     for(int i=n-1;i>=1;i--)
19         for(int j=1;j<=i;j++)
20             for(int k=0;k<=99;k++)
21             f[i][j][k]=f[i+1][j][(k-a[i][j]+100)%100]||f[i+1][j+1][(k-a[i][j]+100)%100];
22     
23     for(int k=100;k>=1;k--)
24         if(f[1][1][k]==1){cout<<k;break;}
25     return 0;
26 }

下面这一段代码可能比较好理解:

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<algorithm>
 4 using namespace std;
 5 int n,a[26][26]={0};
 6 bool f[26][26][102]={0};
 7 int main()
 8 {
 9     cin>>n;
10     for (int i=1;i<=n;i++)           //数据的读入 
11         for (int j=1;j<=i;j++)
12             cin>>a[i][j];
13     
14     for (int i=1;i<=n;i++)          //这一步的代码是为了将最后一排的所有数存下来 
15         f[n][i][a[n][i]]=true;
16     
17     for (int i=n-1;i>=1;--i)        //这里是主要过程,思路:将所有的可能性mod100都存下来, 
18         for (int j=1;j<=i;j++)
19             for (int k=0;k<=99;k++)
20                 {
21                     if (f[i+1][j][k])  f[i][j][(k+a[i][j])%100]=true;
22                     if (f[i+1][j+1][k])    f[i][j][(k+a[i][j])%100]=true;
23                 }
24         for (int k=99;k>=0;--k)       //找出所有可能性中最大的可能,输出,这个题就AC咯、 
25             if (f[1][1][k])
26                 {
27                     cout<<k;
28                     return 0;
29                 }
30 }

 

4829 [DP]数字三角形升级版  &  4832 [DP]数字三角形升级版

(ps:两道题其实是一样的,据说数据规模不同)

题目链接:4829  ,  4832 

题目仍然是由上面第一题的数字三角形变化而来,具体如下:

题目描述 Description

从数字三角形的顶部到底部有很多条不同的路径。对于每条路径,把路径上面的数加起来可以得到一个和,且你有一次机会可以把任何一个数重复加一遍

和最大的路径称为最佳路径。你的任务就是求出最佳路径上的数字之和。

输入描述 Input Description

第一行:一个数,表示行数。

接下来n行为数塔

输出描述 Output Description

一个数即最优结果

样例输入 Sample Input

5

1

1 3

1 1 3

1 1 1 3

7 1 1 1 3

样例输出 Sample Output

18

数据范围及提示 Data Size & Hint

三角形行数不大于1000。最大和不大于maxlongint

算法分析

这个似乎有点类似于背包问题的回溯法求解。可以对动规数组增加一维,用于记录从底层到达(i,j)这个位置的路径上是否曾经有数字被重复使用过。具体参见以下代码:

(代码来自codevs讨论版块

 1 #include<iostream>
 2 using namespace std;
 3 int n;
 4 int a[1000][1000];
 5 int f[1000][1000][2];
 6 int i,j;
 7 int main()
 8 {
 9     cin>>n;
10     for(i=0;i<n;i++)
11       for(j=0;j<=i;j++)cin>>a[i][j];
12     for(i=0;i<n;i++){
13       f[n-1][i][0]=a[n-1][i];
14       f[n-1][i][1]=a[n-1][i]<<1;//f[n-1][i][1]=a[n-1][i]*2;
15       }
16     for(i=n-2;i>=0;i--)
17       for(j=0;j<=i;j++){
18         f[i][j][0]=a[i][j]+max(f[i+1][j][0],f[i+1][j+1][0]);
19         f[i][j][1]=a[i][j]+max(f[i+1][j][1],f[i+1][j+1][1]);
20         f[i][j][1]=max(f[i][j][1],f[i][j][0]+a[i][j]);
21         }
22     cout<<f[0][0][1]<<endl;
23     return 0;
24 }

 

再补充一道数字三角形变形的题目:

Vijos1006 晴天小猪历险记之Hill      、    题解

 

关于数字三角形,还有一题:5585 数字三角形第k优解,暂时未搞懂怎解,希望各位大神留言解答呵呵。

 

参考资料:

【强烈建议阅读】http://blog.csdn.net/Little_Flower_0/article/details/47945611

【强烈建议阅读】http://blog.csdn.net/qq_35776409/article/details/62890528   

http://www.cnblogs.com/zwfymqz/p/6790960.html

http://www.genshuixue.com/i-cxy/p/8020923

 

posted on 2017-08-09 18:00  华山青竹  阅读(5911)  评论(1编辑  收藏  举报

导航