统一计算随机点购买数

时间:2013-09-04 18:54:51

标签: math random discrete-mathematics

在一些流行的桌面游戏中,存在一系列6个统计数据(也称为属性或能力分数)。它们是整数。出于这个问题的目的,它们的数字范围从7到18。

为了保持公平,有一个系统让人们“购买”更高的统计数据。这称为Point Buy。人们从最初的所有属性开始(在这种情况下为7)和一个积分池,然后他们可以花费来增加他们的属性。但是,它不是1-1比率。 18比17等贵得多。

我真正想要做的是,能够编写给定多个点的代码,并为每个属性提供成本,为我提供随机属性集(7到18之间的6个整数),提供的总分。理想情况下,我希望结果能够达到一致性。

为了使问题更容易思考,我可以提供一个从Pathfinder游戏系统中获取的示例。 (请注意,熟悉系统的人可能会注意到事情并不完全相同。我将其简化为计算机科学问题更有意义。)

  

所有属性从7开始,不能再降低。你有44个   花点。将属性提高1的成本为   如下:2,1,1,1,1,1,2,2,3,3,4或者,如果您更喜欢总费用,   2,3,4,5,6,7,9,11,14,17,21。

也欢迎任何类似问题的链接/资源。

3 个答案:

答案 0 :(得分:1)

使用您的示例值来询问此问题的另一种方法是:您可以从集合{0,2,3,4,5,6,7,9,11中抽取多少种方式来替换6个数字,14,17,21}这样总和是44。

获得这些答案后,您可以统一选择一个答案,将它们随机播放到您的属性中,并为每个答案添加7个。

一些示例答案:

  

{21,21,2,0,0,0}     {21,17,6,0,0,0}     {21,17,4,2,0,0}     {21,17,3,3,0,0}     {21,17,2,2,2,0}

所以你选择其中一个,比如{21,21,2,0,0,0}。将其拖入您的6个属性(A到F),例如{21,0,0,2,21,0}。将其映射回您的值{18,7,7,8,18,7}。

注意,改组并不像听起来那么容易,有关于它的讨论有关于它的讨论,并且有一篇有趣的文章:http://www.cigital.com/papers/download/developer_gambling.php

这是正确的(我相信),但不一定有效(或漂亮),C ++来计算你的集合:

#include <vector>
#include <iterator>
#include <iostream>

typedef std::vector<int> Costs;
typedef std::vector<int> Attrs;
typedef std::vector<Attrs> Choices;

void gen(Choices& c, Attrs a, int sum, Costs costs, int attrs) {
  if (sum < 0) { return; }
  if (attrs < 1) {
    if (sum == 0) {
      c.push_back(a);
    }
    return;
  }
  auto cc = costs;
  for (auto cost : costs) {
    a.push_back(cost);
    gen(c, a, sum - cost, cc, attrs - 1);
    a.pop_back();
    cc.erase(cc.begin());
  }
}

Choices genChoices(int sum, const Costs& costs, int attrs) {
  Choices allChoices;
  gen(allChoices, Attrs(), sum, costs, attrs);
  return allChoices;
}


int main(int, char*[]) {
  const Costs costs { 21, 17, 14, 11, 9, 7, 6, 5, 4, 3, 2, 0 };
  const int sum = 44;
  const int attrs = 6;

  auto choices = genChoices(sum, costs, attrs);

  std::cout << choices.size() << "\n";
  for (auto c : choices) {
    std::copy(std::begin(c), std::end(c), std::ostream_iterator<Attrs::value_type>(std::cout, " "));
    std::cout << "\n";
   }

  return 0;
}

使用g ++编译4.7.3:g ++ -std = c ++ 0x -Wall -Wextra attrs.cpp

您提供的示例中有280个。

答案 1 :(得分:0)

最简单的方法是随机生成所有值,然后使用给定的点数检查结果是否可行。如果它没有重新开始。

这可能看起来非常低效,当然是以某种方式,但对于实际应用来说,这是完全没问题的。例如,如果选择可行解决方案的机会是1/6,则预期尝试次数为6次,并且您需要超过40次尝试的机会小于1000次中的1次。

因此,除非你需要做大量的次数(至少> 1000),否则我会推荐这种方法。它易于编码,简短并适用于任何参数。

两次加速:

  1. 如果您的可用积分数较少,则只会生成属性范围极大的属性范围内的属性。因此,如果您有10个点,则只生成7-14范围内的属性。
  2. 您可以在生成属性时检查当前结果是否可行。因此,如果超过前两个属性上可能的点数,则您已经可以丢弃当前结果。丢弃当前配置并重新开始非常重要。如果你试图以某种方式修复它,你最终会得到一个偏斜的分布。
  3. * *编辑刚刚意识到我错过了您所说的“所有积分消费”条件。这使问题变得更加困难,并且这种方法非常低效,因为只有很小一部分可能的配置是可行的。确切的动态取决于您的参数,因此如果您只需要少量样本,您可以尝试这种方法,看看它是否足够快。

答案 2 :(得分:0)

对于这里涉及的一般理论:可能的配置是对背包问题的修改。要统一采样,您可以迭代所有可行的配置并选择然后统一选择其中一个。我希望可行配置的数量相当小,所以如果你有一个快速找到它们的方法,那可以正常工作。

如果有太多可能的配置,因此无法枚举或统计它们,您就会遇到标准问题,即从您所拥有某些信息的分布中采样,但不是全部。我认为解决这个问题的标准方法是使用a Metropolis-Hastings algorithm或其他类型的马尔可夫链蒙特卡罗(另见Propp-Wilson Algorithm)。但是要使用这些方法,您必须在具有某些属性的可行状态之间构建转换函数。我试了一下,回合想不到一个。