【问题标题】:CodeJam 2014: How to solve task "New Lottery Game"?CodeJam 2014:如何解决“新彩票游戏”任务?
【发布时间】:2014-06-21 17:36:50
【问题描述】:

我想知道New Lottery Game 问题的有效方法。

彩票正在改变!彩票曾经有一台机器来生成随机中奖号码。但由于作弊问题,彩票决定增加另一台机器。新的中奖号码将是两台机器产生的两个随机数按位与运算的结果。

要找到 X 和 Y 的按位与,请将它们都写成二进制;如果 X 和 Y 的相应位均为 1,则二进制结果中的某个位为 1,否则为 0。在大多数编程语言中,X 和 Y 的按位与写成 X&Y。

例如: 旧机器生成数字 7 = 0111。 新机器生成数字 11 = 1011。 中奖号码将是 (7 AND 11) = (0111 AND 1011) = 0011 = 3。

通过这项措施,彩票希望减少欺诈索赔的案件,但不幸的是彩票公司的一名员工泄露了以下信息:旧机器将始终生成小于 A 的非负整数和新机器将始终生成一个小于 B 的非负整数。

Catalina 想中奖,为了一试,她决定购买所有小于 K 的非负整数。

鉴于 A、B 和 K,Catalina 想知道机器可以通过多少种不同的方式生成一对数字,从而使她成为赢家。


对于小输入,我们可以检查所有可能的对,但如何处理大输入。我想我们首先将二进制数表示为字符串,然后检查给出的答案小于K 的排列。但我似乎无法弄清楚如何计算 2 个二进制字符串的可能排列。

【问题讨论】:

  • 你知道可以下载大家的解决方案吗?例如,您可以下载第十个第一的解决方案。
  • @ArnaudDenoyelle 是的,我尝试过,但我更喜欢解释。不幸的是,谷歌不发布社论
  • 他们确实发布社论。例如,第 1A 轮已经结束,请耐心等待。
  • @user189 请把我重定向到链接
  • code.google.com/codejam/contests.html 检查“分析”链接。

标签: string algorithm binary


【解决方案1】:

正如 Niklas B. 所说。

整个答案是。

#include <algorithm>
#include <cstring>
#include <iomanip>
#include <iostream>
#include <iterator>
#include <map>
#include <sstream>
#include <string>
#include <vector>

using namespace std;

#define MAX_SIZE 32

int A, B, K;

int arr_a[MAX_SIZE];
int arr_b[MAX_SIZE];
int arr_k[MAX_SIZE];

bool flag [MAX_SIZE][2][2][2];
long long matrix[MAX_SIZE][2][2][2]; 

long long
get_result();

int main(int argc, char *argv[])
{
  int case_amount = 0;
  cin >> case_amount;

  for (int i = 0; i < case_amount; ++i)
  {
    const long long result = get_result();
    cout << "Case #" << 1 + i << ": " << result << endl;
  }

  return 0;
}


long long
dp(const int h,
   const bool can_A_choose_1,
   const bool can_B_choose_1,
   const bool can_K_choose_1)
{
  if (MAX_SIZE == h)
    return can_A_choose_1 && can_B_choose_1 && can_K_choose_1;

  if (flag[h][can_A_choose_1][can_B_choose_1][can_K_choose_1])
    return matrix[h][can_A_choose_1][can_B_choose_1][can_K_choose_1];

  int cnt_A_max = arr_a[h];
  int cnt_B_max = arr_b[h];
  int cnt_K_max = arr_k[h];

  if (can_A_choose_1)
    cnt_A_max = 1;

  if (can_B_choose_1)
    cnt_B_max = 1;

  if (can_K_choose_1)
    cnt_K_max = 1;

  long long res = 0;

  for (int i = 0; i <= cnt_A_max; ++i)
  {
    for (int j = 0; j <= cnt_B_max; ++j)
    {
      int k = i & j;

      if (k > cnt_K_max)
        continue;

      res += dp(h + 1,
                can_A_choose_1 || (i < cnt_A_max),
                can_B_choose_1 || (j < cnt_B_max),
                can_K_choose_1 || (k < cnt_K_max));
    }
  }

  flag[h][can_A_choose_1][can_B_choose_1][can_K_choose_1] = true;

  matrix[h][can_A_choose_1][can_B_choose_1][can_K_choose_1] = res;

  return res;
}

long long
get_result()
{
  cin >> A >> B >> K;

  memset(arr_a, 0, sizeof(arr_a));
  memset(arr_b, 0, sizeof(arr_b));
  memset(arr_k, 0, sizeof(arr_k));

  memset(flag, 0, sizeof(flag));
  memset(matrix, 0, sizeof(matrix));

  int i = 31;
  while (i >= 1)
  {
    arr_a[i] = A % 2;
    A /= 2;

    arr_b[i] = B % 2;
    B /= 2;

    arr_k[i] = K % 2;
    K /= 2;

    i--;
  }


  return dp(1, 0, 0, 0);
}

【讨论】:

    【解决方案2】:

    我所做的只是确定答案何时是 A * B。 否则,只需暴力破解其余部分,此代码通过大量输入。

    // for each test cases
    long count = 0;
    if ((K > A) || (K > B)) {
        count = A * B;
        continue; // print count and go to the next test case
      }
    
    count = A * B - (A-K) * (B-K);
    
    for (int i = K; i < A; i++) {
      for (int j = K; j < B; j++) {
        if ((i&j) < K) count++;
      }
    }
    

    我希望这会有所帮助!

    【讨论】:

    • 这对于像1000000000 1000000000 1 这样的测试用例很容易破坏。似乎测试用例很弱。我刚查了一下,练习数据集确实不包含K &lt;&lt; AK &lt;&lt; B 和 A 和 B 都很大的案例。这很奇怪。
    【解决方案3】:

    我使用了一种通用的 DP 技术,我详细描述了 in another answer

    我们要计算 (a, b) 对,使得 a

    第一步是将数字转换为二进制并通过添加前导零将它们填充为相同的大小。我只是将它们填充到 40 的固定大小。这个想法是一点一点地建立有效的 a 和 b。

    f(i, loA, loB, loK) 为大小为 40 - i 的 a 和 b 的有效后缀对数。如果 loA 为真,则意味着直到 i 的前缀已经严格小于 A 的相应前缀。在这种情况下,对 a 的下一个可能位没有限制。如果 loA ist false,A[i] 是我们可以放在当前前缀末尾的下一位的上限。 loB 和 loK 有类似的含义。

    现在我们有以下过渡:

    long long f(int i, bool loA, bool loB, bool loK) {
      // TODO add memoization
      if (i == 40)
        return loA && loB && loK;
      int hiA = loA ? 1: A[i]-'0';  // upper bound on the next bit in a
      int hiB = loB ? 1: B[i]-'0';  // upper bound on the next bit in b
      int hiK = loK ? 1: K[i]-'0';  // upper bound on the next bit in a & b
      long long res = 0;
      for (int a = 0; a <= hiA; ++a)
        for (int b = 0; b <= hiB; ++b) {
          int k = a & b;
          if (k > hiK) continue;
          res += f(i+1, loA || a < A[i]-'0',
                        loB || b < B[i]-'0',
                        loK || k < K[i]-'0');
        }
      return res;
    }
    

    结果是f(0, false, false, false)

    如果添加 memoization 以确保每个子问题只解决一次,则运行时间为 O(max(log A, log B))

    【讨论】:

    • 非常好的解释和方法。链接中的优秀答案和问题也是如此
    • 是否可以通过另一种方法。虽然这种方法绝对没问题,但我想知道解决这个问题的不同方法。
    • @TejasPatel 您可以强制 a、b 和 a & b 的最大前缀的大小等于 A、B 和 K 中的相应前缀。您可以假设下一位需要小于A和B中对应的位。其余位可以任意选择。这是相同的方法,只是更明确,但更慢且更难实现。
    • 请注意,必须使用 DP 或记忆化来获得对数复杂度。否则,答案中定义的函数具有 O(AB) 复杂度。您可以看到如下:如果 loA = true 且 hiA = 1,那么对于 a = 0 和 a = 1,将计算相同的 f。
    • @SPrasanth 当然,这就是我在回答的第一句话中提到 DP 的原因
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-07-17
    • 1970-01-01
    • 1970-01-01
    • 2019-04-20
    • 1970-01-01
    相关资源
    最近更新 更多