【问题标题】:How to apply recursion如何应用递归
【发布时间】:2015-11-17 05:35:25
【问题描述】:

问题陈述:

您位于 (x1,x2,...,xN) 位置的 N 维网格中。网格的尺寸为 (D1,D2,...DN)。一步,你可以在 N 个维度中的任何一个维度上向前或向后走一步。 (所以总是有 2×N 可能的不同动作)。你有多少种方法可以走 M 步,这样你在任何时候都不会离开网格?如果在任意点 xi,xi≤0 或 xi>Di,则离开网格。

输入格式

第一行包含测试用例 T 的数量。接下来是 T 测试用例。对于每个测试用例,第一行包含 N 和 M,第二行包含 x1,x2,…,xN,第三行包含 D1,D2,…,DN。

输出格式

输出 T 行,一个对应每个测试用例。由于答案可能非常大,因此以 1000000007 为模输出。

约束

1≤T≤10

1≤N≤10

1≤M≤300

1≤Di≤100

1≤xi≤Di

示例输入

1

2 3

1 1

2 3

样本输出

12

如果这是 1D 的解决方案可以是这样的:solve(i+1)+solve(i-1);

2D中:solve(i+1,j)+solve(i-1,j)+solve(i,j+1)+solve(i,j-1);我如何为 N 维编程?他们是否有一些用于制作上述递归语句的一般步骤,这有助于制作递归语句

我看到的大多数解决方案都是自下而上或自上而下的方式,我无法理解它们?是他们理解他们的任何方式吗,因为我一直使用递归 + 记忆练习 dp,我发现很难理解他们

如何做记忆?使用哪种数据结构?,在哪里使用模 1e 7(仅在最终答案中)??

更新 感谢 BARNEY 的解决方案,但在大多数情况下获得 TLE 如何更快地做到这一点 代码:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.HashMap;

public class Grid_Walking {
    private static String[] n;
    private static int moves;

    private static HashMap<String, Integer> hm;
    private static BufferedReader br;

    private static String[] s;

    private static int dimen;
    private static int[] present;
    private static int[] dimlen;

    public static void main(String args[]) throws IOException {

        br = new BufferedReader(new InputStreamReader(System.in));
        int t = Integer.parseInt(br.readLine());
        while (t > 0) {
            n = br.readLine().split(" ");
            dimen = Integer.parseInt(n[0]);
            present = new int[dimen];
            dimlen = new int[dimen];
            moves = Integer.parseInt(n[1]);

            s = br.readLine().split(" ");
            for (int i = 0; i < dimen; i++) {
                present[i] = Integer.parseInt(s[i]) - 1;
            }
            s = br.readLine().split(" ");
            for (int i = 0; i < dimen; i++) {
                dimlen[i] = Integer.parseInt(s[i]);
            }
            hm = new HashMap<String, Integer>();
            System.out.println(solve(present, moves) % 1000000007);
            t--;
        }

    }

    private static int solve(int[] x, int moves) {
        // TODO Auto-generated method stub
        int result = 0;
        String s = Arrays.toString(x) + moves;
        if (hm.containsKey(s)) {
            return hm.get(s);
        }
        if (moves == 0) {
            return 1;
        }
        for (int i = 0; i < dimen; i++) {
            // System.out.println("fjfvnfjbv");
            if (x[i] > 0) {
                x[i] = x[i] - 1;
                result = result + solve(x, moves - 1);
                x[i] = x[i] + 1;
            }
            if (x[i] < dimlen[i] - 1) {
                x[i] = x[i] + 1;
                result = result + solve(x, moves - 1);
                x[i] = x[i] - 1;
            }
        }
        hm.put(s, result % 1000000007);
        return result % 1000000007;
    }
}

【问题讨论】:

  • @Barney 递归工作

标签: recursion dynamic-programming


【解决方案1】:

使用两级 DP 方法可以通过以下方式有效地解决此问题:

首先,分别解决每个维度的问题。这意味着对于每个维度,仅计算在该维度中进行 0、1、...、M 步的方式数(从给定的起始位置开始)。您可以按照您在提议的 DP Java 代码中描述的方式来解决这个问题。例如,您可以将解决方案存储在整数矩阵中,其中行数对应于维数,列数为 M+1。请注意,对于每个维度,您不仅必须计算有多少种方法可以进行 M 步,还必须计算有多少种方法可以进行 M-1、M-2、... 和 0 步。我们需要此信息用于第二级。

其次,仅使用前 j 个维度计算 0、1、...、M 步的方法数。假设您已经计算了如何仅使用前 j-1 个维度进行 0、1、...、M 步的方法数。通过使用前 j 个维度,有多少种方法可以使 i 步?

  • 您可以在维度 j 中进行 0 步,在维度 中进行 i 步
  • 或维度 j 1 步和维度
  • ...
  • 或在维度 j 中 i-1 步和在维度
  • 或在维度 j 中执行 i 步,在维度

假设您在维度 j 中执行 i-k 步,在维度

请注意,您必须以 1000000007 为模执行计算。为了计算以素数为模的二项式系数,您可能会发现 link 很有帮助。

【讨论】:

  • 太棒了!感谢您分享这个!
【解决方案2】:

我认为这其中的关键在于,从任何位置来看,每个维度都可能有 0、1 或 2 种可能的移动:

  • 如果您在该维度的中间,则移动 2 次​​li>
  • 如果您在其中一个边缘,则移动 1 步
  • 如果维度只有大小 1(即你在两边),则 0 移动

例如,在二维中你有类似的东西:

+---+---+---+---+
+ a +   +   +   +
+---+---+---+---+
+   +   + c +   +
+---+---+---+---+
+   + b +   +   +
+---+---+---+---+
  • 您可以在两个维度上移动一个位置
  • 从 b 开始,您可以在垂直维度上移动一个位置,在水平方向上移动两个位置
  • 从 c 可以在两个维度上移动两个位置

因此,您可以进行的总移动次数是从当前位置移动的次数,加上从每个新位置移动的总数。第二部分是递归。

所以类似于下面的伪代码:

N is number of dimensions
D is array 1 to N of dimensions
X is array 1 to N of current position

int solve(array D, array X, int moves) {

    // base case when no more moves possible
    if (moves == 0)
        return 1

    // memo key is current position plus number of moves - this code is
    // java and creates a string similar to "[2, 3]4"
    String memoKey = Arrays.toString(X) + String.valueOf(moves);
    if (hashMap.containsKey(memoKey)) {
      return hashMap.get(memoKey);
    }

    // accumulate results from all possible moves in all dimensions
    int result = 0

    // check each dimension
    for n = 1 to N {

        // if we are not at the start of this dimension
        // then add all moves for a decrease of one place
        if (X[n] > 1) {
            X[n] = X[n] - 1
            result = result + solve(D, X, moves - 1)
            X[n] = X[n] + 1
        }

        // if we are not at the end of this dimension
        // then add all moves for a increase of one place
        if (X[n] < D[n]) {
            X[n] = X[n] + 1
            result = result + solve(D, X, moves - 1)
            X[n] = X[n] - 1
        }
    }

    // memoise result for later
    hashMap.put(memoKey, result);

    return result
}

更新

包含记忆 - 可能不是最有效的,但它解释了一种相当简单的方法。

【讨论】:

  • secong if for 是什么?,它不会加倍答案吗?
  • @sesy 第一个if 检查当前维度是否可能减少——即我们不是该维度的起点。如果是这样,那么它将在该维度中“向下”移动一个位置并递归。第二个if 做同样的事情,只是为了在那个维度上“向上”移动。根据位置和尺寸大小,测试可能成功或失败,因此您会得到答案中提到的 0、1 或 2 个可能的移动。有帮助吗?
  • 谢谢!!递归有效,我试图应用记忆,但它似乎不起作用,只适用于小输入,我不知道为什么,在大多数情况下获得 TLE,你能检查一下并建议一些更好的记忆方法吗?
  • @Berney 删除了解决方案,因为它包含太多错误你能建议更好的记忆方法
  • @sesy 确定 - 我将用于备忘录的关键是当前位置加上移动次数(从 [1,1] 向左移动与 [1,1] 的值不同左移两步)。我已更新示例代码以包含 Java 实现。
【解决方案3】:

N维数组可以展平为一维数组索引,最终索引的大小为D1*D2*...*DN。公式类似如下:

// this take (x1,x2,...xn) & (D1, D2, ..., DN) to produce single index.
uint64_t flatten(int N, int x[], int D[]) {
  uint64_t index = 0, mult = 1;
  for (int i = N-1; i>0; i--) {
    index += x[i];
    index *= D[i-1];
  }
  index += x[0];
  return index;
}

// this is the reverse
void unflatten(int N, int x[], int D[], uint64_t index) {
  for (int i = 0; i < N - 1; i++) {
    x[i] = index % D[i];
    index /= D[i];
  }
  x[N-1] = index;
}

用这种方式来实现你的 N 维动态规划。

【讨论】:

  • 能不能多解释一下没明白,怎么弄平
  • 让索引成为最终的扁平化索引。如果只有 1 个维度,则 index = x1。如果有 2 个维度,假设 D1 = 5,D2 = 6,那么 (x1,x2) 将映射到 (x2 * 5 + x1)。如果有 3 个维度,则 (x1, x2, x3) 将映射到 (x3 * D2 + x2) * D1 + x1。使用这种方式可以将 N 维索引转换为 1 维索引。
  • 如果N维索引可以转换为一维数组索引,那么动态规划就容易多了。 @Barney 给出的答案是使用递归方法,但仍然很难将其转换为动态编程方式。请注意,动态规划的复杂度为 O(D1*D2*...*DN * N * moves),比复杂度为 O((2*N)^moves) 的递归算法快得多。跨度>
【解决方案4】:

这是一个 Java 解决方案,它基本上通过逐个计算方式来计算方式。大约 8 秒内计算出 10 亿种方式。

long compute(int N, int M, int[] positions, int[] dimensions) {
    if (M == 0) {
        return 1;
    }
    long sum = 0;
    for (int i = 0; i < N; i++) {
        if (positions[i] < dimensions[i]) {
            positions[i]++;
            sum += compute(N, M - 1, positions, dimensions);
            positions[i]--;
        }
        if (positions[i] > 1) {
            positions[i]--;
            sum += compute(N, M - 1, positions, dimensions);
            positions[i]++;
        }
    }
    return sum % 1000000007;
}

【讨论】:

    猜你喜欢
    • 2018-12-14
    • 2019-09-30
    • 2020-06-20
    • 2019-03-23
    • 2012-04-11
    • 2012-05-14
    • 2014-04-07
    • 2020-11-21
    • 2022-12-20
    相关资源
    最近更新 更多