USACO数字三角形

时间:2013-05-25 14:24:17

标签: c algorithm optimization data-structures depth-first-search

问题如下

考虑下面显示的数字三角形。编写一个程序,计算可以在从顶部开始到底部某处的路径上传递的最大数字总和。每个步骤都可以向左斜下方或向右斜下方。

        7

      3   8

    8   1   0

  2   7   4   4

4   5   2   6   5

在上面的示例中,路由7 -> 3 -> 8 -> 7 -> 5产生最高总和:30。

我遇到以下错误

 Execution error: Your program (`numtri') used more than the
    allotted runtime of 1 seconds (it ended or was stopped at 1.674
    seconds) when presented with test case 6. It used 6080 KB of
    memory. 

我的程序适用于输入< = 8三角形大小。但是,当三角形大小超过8时,它就失败了。 为什么会这样,我不知道。请帮忙。

这是我的代码:

#define MAX 1000

int max=0,a[MAX][MAX];

void dfs(int i,int j,int end,int sum)
{
 if(i<=end)
 {   
      sum += a[i][j];
      dfs(i+1,j,end,sum);
      dfs(i+1,j+1,end,sum);
 }
 else 
 {
      if(sum>max)  
      max = sum;

 }
}

int main () {

FILE *fin  = fopen ("numtri.in", "r");
FILE *fout = fopen ("numtri.out", "w");
int r,i,j;

fscanf(fin,"%d",&r);

for(i = 1;i<=r;i++)
 for(j = 1;j<=i;j++)
  fscanf(fin,"%d",&a[i][j]);

dfs(1,1,r,0);

fprintf(fout,"%d\n",max);

fclose(fin);
fclose(fout);
return 0;
}

它适用于前5个测试用例,但在第6个失败时有199个三角形大小。

4 个答案:

答案 0 :(得分:2)

每当您的程序遇到金字塔中的特定点时,它会计算到底部的最佳路径。但是,您可以观察到每个点遇到不止一次,因此计算出最佳路径数次。因此,您的程序将以指数时间运行。

如果您改为保存三角形中某个点(此处为dp[i][j])可实现的最大总和,并重新使用该值而不是重新计算它,一旦再次点击该点,您的程序将会快得多。那是因为该算法只访问金字塔中的每个点一次。这称为自上而下的动态编程

#include<string.h>
#include<stdio.h>
#define MAX_N 1005

int a[MAX_N][MAX_N];
int dp[MAX_N][MAX_N];

int max(int a, int b)
{
  return a > b ? a : b;
}

int dfs(int i,int j,int end)
{
  if(dp[i][j] != -1)
  {
    return dp[i][j];
  }
  else if(i <= end)
  {
    return dp[i][j] = a[i][j] + max(dfs(i+1,j,end), dfs(i+1,j+1,end));
  }
  else
  {
    return 0;
  }
}

int main () {
  FILE *fin  = fopen ("numtri.in", "r");
  FILE *fout = fopen ("numtri.out", "w");
  int r,i,j;

  memset(dp, -1, sizeof dp);

  fscanf(fin,"%d",&r);

  for(i = 1;i<=r;i++)
    for(j = 1;j<=i;j++)
      fscanf(fin,"%d",&a[i][j]);

  fprintf(fout,"%d\n", dfs(1,1,r));

  fclose(fin);
  fclose(fout);
  return 0;
}

答案 1 :(得分:1)

使用DFS解决此问题的效率很低,原因如下:考虑一条路径先行,然后左转,另一条路径先左转,然后右转。这些路径现在位于同一位置,从此位置引出的路径将计算两次。在金字塔的较低层次,情况更糟,给出了指数运行时间。

您需要做的是动态编程。我们使用这个问题展示最优子结构(对于最大路径必须是最大的所有子路径)和重叠子问题(上述行为)的事实。这使我们可以避免不必要的工作。

有两种可能的方法。

  1. 自上而下通过记忆:执行dfs,但在返回时保存给定单元格的计算值。这样当您再次访问一个单元格时,您不必从该单元格执行dfs,并且可以立即返回。

  2. 自下而上:从底行开始,并保留从当前行中的每个单元格开始可实现的最大总和的列表。首先,这只是底部的数字。然后,对于下一行,第i行的单元格j将具有最大总和:a[i][j] + max(maxsum[i+1][j], maxsum[i+1][j+1])

  3. 有关更多信息,请阅读维基百科或您最喜欢的算法书中的动态编程。

答案 2 :(得分:0)

dfs()例程中的递归赋予dfs一个在搜索深度呈指数级的运行时,其中大部分工作都是冗余的。 对于这个问题,有一个简单的非递归解决方案。而不是递归到dfs,维护当前级别路径最大值的向量v。要更深入一级,请将v复制到工作向量w;然后将每个元素v[j]设置为a[i][j]加上较大的w[j]w[j+1]

答案 3 :(得分:0)

BFS可以代替dp来使用,因为遇到访问的节点时,您可以简单地更改其值,因为直到图的末尾才遍历,但必须非常注意内存。这是我在USACO平地机上正常工作的代码:

#include <bits/stdc++.h>
    using namespace std;
    
    typedef long long ll;
    typedef long double ld;
    typedef vector<long long> vi;
    typedef pair<long long,long long> pi;
    typedef vector<pi> vpi;
    
    #define FOR(i, a, b) for(ll i=ll(a); i<ll(b); i++)
    #define ROF(i, a, b) for(ll i=ll(a); i>=ll(b); i--)
    #define f first
    #define s second
    #define pb emplace_back
    #define mp make_pair
    #define SQ(a) (a)*(a)
    #define all(a) (a).begin(), (a).end()
    
    
    int main() {
    ifstream cin("numtri.in");
    ofstream cout("numtri.out");
    int n;
    
    int adjacency_list[500500];
    int matrix[1005][1005];
    
    bool visited[500500];
    int value[500500];
    
    int total[500500];
    int maxnum=0;
    
    cin>>n;
    int s=0,x=0;
    for(int i=0;i<n;i++){
    s++;
    for(int j=0;j<s;j++){
      cin>>value[x];
      total[x]=0;
      matrix[i][j]=x;
    x++;
    }
    }
    s=0;
    
    
    for(int i=0;i<n-1;i++){
    s++;
    for(int j=0;j<s;j++){
      adjacency_list[matrix[i][j]]=(matrix[i+1][j]);
    
    }
    }
 
    
     
    total[0]=value[0];
    queue<int>q;
    q.push(0);
    while(!q.empty()){
      int current=q.front();
      q.pop();
    
    if(current<((n*(n+1) )/2)-1-(n-1 )  ){
    
    int i =adjacency_list[current];
      if(!visited[i]){
        q.push(i);
        total[i]=total[current]+value[i];
        //cout<<total[i];
      }
      else{
    if(total[current]+value[i]>total[i]){
      total[i]=total[current]+value[i];
    }
      }
      maxnum=max(total[i],maxnum);
      visited[i]=1;
    
    i =adjacency_list[current]+1;
      if(!visited[i]){
        q.push(i);
        total[i]=total[current]+value[i];
       
      }
      else{
    if(total[current]+value[i]>total[i]){
      total[i]=total[current]+value[i];
    }
      }
      maxnum=max(total[i],maxnum);
      visited[i]=1;
    }
    }
    
    cout<<maxnum<<endl;
        return 0;
    }