【问题标题】:USACO number triangleUSACO 数字三角形
【发布时间】:2013-05-20 23:16:45
【问题描述】:

问题如下

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

        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. 

我的程序适用于输入

这是我的代码:

#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 个三角形大小。

【问题讨论】:

  • 向 DFS 添加 memoization 或使用动态编程可以使您的程序更快。因为对于固定的i,j,从(i,j)到底部的最优路径也是固定的。因此,一对 (i,j) 只需要一个 DFS。如果您需要第二次 DFS,只需使用之前搜索的结果即可。
  • 我在这里写这篇文章是因为它与你的问题没有直接关系,但如果你想做编程(尤其是像 topcoder 之类的东西),你真的应该学习算法和数据结构。许多 [谁?] 认为有关该主题的规范书籍是 Cormen 等人的算法简介,我建议你拿起它。
  • 如果我没记错的话,如果 r1000 你的程序会调用未定义的行为...

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


【解决方案1】:

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

如果您改为保存三角形中某个点(此处为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;
}

【讨论】:

  • 我想这是我需要的,但它显示上述测试用例的错误输出。
  • 我在 dp 表中设置了错误的值。已经修好了!
【解决方案2】:

使用 DFS 解决此问题效率低下,原因如下:考虑一条先右后左的路径,另一条先左后右的路径。这些路径现在位于同一个点,从该点开始的路径将被计算两次。在金字塔的较低层,情况更糟,运行时间呈指数增长。

你需要做的就是所谓的动态规划。我们利用这个问题表现出最优子结构(要使路径最大,所有子路径必须最大)和重叠子问题(上述行为)这一事实。这可以让我们避免做不必要的工作。

有两种可能的方法。

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

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

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

【讨论】:

    【解决方案3】:

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

    【讨论】:

      【解决方案4】:

      可以使用 BFS 代替 dp ,因为当您遇到访问过的节点时,您可以简单地更改其值,因为您直到图结束才遍历,但您必须非常小心内存。这是我在 USACO Grader 中可以使用的代码:

      #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;
          }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2010-10-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多