【问题标题】:Efficiently solving this recurrence relation. [ Better than O(N^2) ]有效地解决了这种递归关系。 [ 优于 O(N^2) ]
【发布时间】:2012-10-06 10:42:00
【问题描述】:

我有一个类似这样的循环关系:

E( i, j ) = A[ i ] + A[ j ] + E( i+1, j-1 ) + E( i+2, j ) + E( i, j-2 ) ;如果我!= j

E( i, j ) = A[ i ] ;如果(我 == j)

E( 我, j ) = 0 ;如果 i
A[ 我 ] 1

我想在时间复杂度上比 O(N^2) 更好地求解 E(1, N)。我想知道我们是否可以找到一些用于这种递归的封闭式公式,或者可能是一种计算 E(1, N) 值的聪明方法?

欢迎任何提示,
提前致谢。

编辑:
实际问题是这样的:

  1. 我们有一个数组 A[],2 名玩家轮流玩这个游戏。
  2. 玩家可以选择概率为 0.5 的最左边或最右边的数字并将其添加到他的分数中(如果数组中剩下单个元素,他将简单地选择该数字并结束游戏)。
  3. 问题是找出 Player1 将达到的预期分数。

【问题讨论】:

  • 我对此表示怀疑。不过,您也许可以找到解决根本问题的不同方法,所以我认为您应该发布该内容。
  • 好的,我会在一分钟内发布这个问题,但是假设我们有一个有效的替代方法来解决潜在问题,那么这种方法不应该在这里反映出来解决这种重复问题吗?有效率的?或者不一定!
  • 问题看起来像 stackoverflow.com/questions/12758652 的副本。
  • Nabb:是的,同样的问题,但显然有比 O(N^2) 更好的方法。我已经尝试过 DP 与 memoization。
  • @srbh.kmr 是什么让你认为它可以在少于 O(n^2) 的时间内完成?

标签: algorithm complexity-theory recurrence


【解决方案1】:

关于你的重复关系:

这似乎有些困难,因为文章表明,对于 E(1,N),我们需要 E(2,..) 和 E(3,..),而其中的每一个都需要其他条目更高的“i”等等。

现在,重新排列这些术语,以便我们将具有最高“i”的 E 项隔离(一般来说,递归关系的一个好主意是查看不同的排列来隔离特定索引 - 最高,最低的,中间的,...):

E(i+2,j) = E(i,j) - E(i,j-2) - E(i+1,j-1) - A(i) - A(j)

然后,将所有 i 下移 2(只需重写公式)

E(i,j) = E(i-2,j) - E(i-2,j-2) - E(i-1,j-1) - A(i-2) - A( j)。

然后,对于 {i=1, j=N},这就是您要查找的内容,我们得到:

E(1,N) = E(-1,N) - E(-1,N-2) - E(0,N-1) - A(-1) - A(N) = -A (N),因为所有其他项都为零。

(当然,我假设 E(i,j) 和 A(i) 对于 i=0 / j=0 为零;当您仅为 NEGATIVE 索引指定零值时,您的规范可能不完整,但不适用于零指数)。

然后,关于您对基础案例的(已编辑的)描述 - 两名玩家从 A 轮流参加,从左或右任意,直到 A 用尽:

从之前的结果来看,我认为你的递归关系不太可能描述潜在的游戏,给定结果 -A(N)... 因此,将递归关系放在一边(无论如何 i=1 都已解决) )。看看游戏本身,我们可能会得出什么关系。

[编辑后继续]

到目前为止,我还没有想出可以以封闭形式进行转换的东西,即比处理某种递归关系更快的东西。因此,现在让我们写下我们如何以适合计算的方式描述游戏。

定义以下数量:

令 X(i,k) 为在第 #k 回合结束时项目#i(在数组 A 中)仍然存在(即尚未被任何玩家拿走)的概率(计算两个玩家的回合数)。当然,k 在我们的游戏中从 1 运行到 N,i 也是如此。出于验证目的,我们可能会注意到,对于所有 i,X(i,N) 必须为零:在游戏结束时,所有元素都已被取走。

如果我们可能需要(在公式评估中)任何超出“正常”范围的 X(i,k) 值,我们会评估:

X(i,k)=1 for {1<=i<=N; k=0}: initially all elements in A are still there.
X(i,k)=0 whenever i<1 or i>N: no elements ever exist outside the (original) range of A.

接下来,让 T(i,k) 是在第 #k 回合恰好被(任何玩家)拿走元素 #i 的概率。 那必须是 0.5 * 它是最左边元素(当前)的概率,这相当于说元素 #(i-1) 在 PREVIOUS 回合结束时不存在,加上 0.5 * 它是最左边元素的概率最右边的元素(当前),这又等于说元素#(i+1) 在上一轮结束时不存在,而所有这些都必须乘以元素#i 本身在第一轮中存在的概率地点:

T(i,k) = X(i,k-1) * ( (1-X(i-1,k-1)) + (1-X(i+1,k-1)) ) / 2

元素#i在第k轮之后仍然存在的概率X(i,k)是它在上一轮结束时存在的概率减去它在第k轮被取走的概率:

X(i,k) = X(i,k-1) - T(i,k)

元素#i 与玩家#1 结束的概率是 T(i,k) 的所有回合 k 的总和,但仅计算 HIS 回合。让我们用 P(i,1) 来表示这个数量,其中 1 代表玩家 #1。

P(i,1) = sum{ k=1,3,...: T(i,k) }

同样,对于玩家 #2:

P(i,2) = sum{ k=2,4,...: T(i,k) }

那么玩家#1 的期望得分就是总和 S(1):

S(1) = sum( i=1..N: P(i,1)*A(i) }, and likewise for player #2: S(2) = sum( i=1..N: P(i,2)*A(i) }

看看上面的公式,我看不出有什么方法可以避免时间上的 O(N2) 方法。内存使用可能是 O(N),因为我们可能会继续运行总计并丢弃不再需要的旧“元素”数据。鉴于此 - 假设您没有处理过长的数组 A - 玩家将无法生存! - O(n2) 时间性能在实践中不应该成为这样的问题。

int N  = sizeof(A);

// note on the code below: we'll use i as an integer running over the elements in A, and k running over the turns that players make.
// while in human parlance we would have them (both) run from 1 to N, the zero-based arrays of C++ make it more convenient to let them 
// run from 0 to N-1.

// initialize the running totals P(i), the accumulated (over turns) probabilities that element #i winds up with player #1.
// if we ever need the same for player #2, it's values are of course 1-P (that is: at the end of the game).
double* P = new double[N];
for (int i=0; i<N; i++)
{
  P[i] = 0; // before we start, there's little chance that player #1 has already taken any element.
}
// initialize the existence-array for the elements.
int* X = new int[N];
for (int i=0; i<N; i++)
{
  X[i] = 1; // initially all elements exist, i.e. have not yet been taken by any player.
}
// declare an array for processing the probabilities that elements are taken at a particular turn.
double* T = new double[N];

// iterate over the turns.
for (int k=0; k<N; k++)
{
  // for each element, calculate the probability that it is taken NOW.
  // the current values of X - the existence array - refer to the (end of the) previous turn.
  for (int i=0; i<N; i++)
  {
    // note: take care of the boundaries i=0 and i=N-1.
    if (i == 0)
    {
      T[i] = X[i] * ( 2 - X[i+1] ) / 2;
    }
    else if (i == N-1)
    {
      T[i] = X[i] * ( 2 - X[i-1] ) / 2;
    }
    else
    {
      T[i] = X[i] * ( 2 - X[i-1] - X[i+1] ) / 2;
    }
  } // element i
  // with the take-probabilities for this turn in place, update P - only at odd turns (i.e. k=even): P is for player #1 only.
  if (k % 2 == 0)
  {
    for (int i=0; i<N; i++)
    {
      P[i] += T[i];
    }
  }
  // finally - in this turn - update the existence array.
  for (int i=0; i<N; i++)
  {
    X[i] -= T[i];
  }

} // turn k

// result: calculate the expected score for player #1.
double score = 0;
for (int i=0; i<N; i++)
{
  score += P[i] * A[i];
}

--

【讨论】:

  • 伯特:感谢您的透彻分析。我认为打破这种重复的是'i down by 2'的移动,因为递归是以自下而上的方式定义的,而将 i 移动到 i-2 是试图根据已经未知的值来定义一些值。我认为你的其余分析结合起来,有点类似于我的重复:E(i, j) = 0.5 * A[i] + 0.5 * A[j] + 0.5 * E(i+1, j-1) + 0.25 * E(i+2, j) + 0.25 * E(i, j-2). 其中,E(i, j) 是 player1 可以使用子数组A[i, j] 实现的预期分数,可以使用O(N^2) 时间复杂度和O(N) 实现空间。
  • 嗯,很高兴我能帮助你一些。请记住,您可以以自下而上的方式设计递归关系,但它始终是在所有方向上定义的。这意味着您必须始终考虑相关索引的边界,因此可能需要提供额外的定义以确保递归关系完全有效。
【解决方案2】:

由于概率相等,这只是一个计数问题。

你能算出在第 k^ 回合选择 A[j] 的机会吗?

【讨论】:

  • 我没有看到任何证据表明这是一个答案。
  • @david:如果你试着再读一遍这个问题?它清楚地表明,欢迎任何提示......
  • 即便如此。你给了什么提示?
  • @david:我所暗示的正是我所写的:尝试计算 A[j] 在第 k^th 回合被选中的机会。我相信我们甚至可以为此给出一个封闭形式的公式。事实上,这似乎是另一个答案中缺少的一步。
  • 那条评论听起来比你的回答好。我只是想帮助你。接受与否取决于你。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-10-10
  • 1970-01-01
  • 2011-03-14
  • 2021-02-07
  • 1970-01-01
相关资源
最近更新 更多