【问题标题】:Understanding the "Cow Id" coding competition solution了解“Cow Id”编码竞赛解决方案
【发布时间】:2019-07-25 05:59:10
【问题描述】:

我了解解决方案的 DP 部分,但我不了解“回溯”部分,以及使用 i 设置位查找 j 长度数字的数量如何帮助解决问题?解决方案的粗体部分让我感到困惑。

问题:

Being a secret computer geek, Farmer John labels all of his cows with
binary numbers. However, he is a bit superstitious, and only labels cows
with binary numbers that have exactly K "1" bits (1 <= K <= 10).  The
leading bit of each label is always a "1" bit, of course. FJ assigns
labels in increasing numeric order, starting from the smallest possible
valid label -- a K-bit number consisting of all "1" bits. Unfortunately,
he loses track of his labeling and needs your help: please determine the
Nth label he should assign (1 <= N <= 10^7).

INPUT FORMAT:

* Line 1: Two space-separated integers, N and K.

SAMPLE INPUT (file cowids.in):

7 3

INPUT DETAILS:

Among all binary numbers containing exactly 3 "1" bits, FJ wants to output
the 7th in increasing sorted order.

SAMPLE OUTPUT (file cowids.out):

10110

解决方案:

这个问题可以通过动态规划来解决。我们将 K=1 的输入视为一种特殊情况,因为这仅涉及打印一个 1 后跟 N-1 个零。对于 K 至少 2,快速粗略计算告诉我们答案中的总位数最多为 5000。对于二维数组 A[0..10][0.. 5000],我们让 A[i][j] 表示正好有 i 个 1 位的 j 位二进制数(包括以前导零开头的那些)的数量。我们可以通过设置 A[i][j] = A[i-1][j-1] + A[i][j-1] 来填写此表,因为具有 i 个 1 位的 j 位数字可以通过将 0 位附加到具有 i 个 1 位的 (j-1) 位数字,或通过将 1 位附加到具有 (i-1) 个 1 位的 (j-1) 位数字来获得。 填完表格后,从 A[K][5000] 开始的适当“回溯路径”会为我们提供我们要寻找的二进制数(注意不要打印前导零)。 "

解决方案代码:

#include <stdio.h>
#define M 5000

int A[11][M+1];
int leading_zeros = 1;

void print_sol(int n,int k,int m)
{
  if (k==0 && m==1) return;
  if (k==0 || A[k][m-1] >= n) {
    if (!leading_zeros) printf ("0");
    print_sol(n,k,m-1);
  } else {
    leading_zeros = 0;
    printf ("1");
    print_sol(n-A[k][m-1],k-1,m-1);
  }
}

int main(void)
{
  int i,j,N,K;

  freopen ("cowids.in", "r", stdin);
  freopen ("cowids.out", "w", stdout);

  scanf ("%d %d", &N, &K);

  if (K==1) {
    printf ("1");
    for (i=0; i<N-1; i++) printf ("0");
    printf ("\n");
    return 0;
  }

  A[0][1] = 1;
  for (j=1; j<=M; j++) {
    for (i=0; i<=10; i++) {
      if (i==0) A[i][j] = 1;
      else A[i][j] = A[i-1][j-1] + A[i][j-1];
      if (A[i][j] > 10000000) A[i][j] = 10000000; /* avoid overflow */
    }
  }

  print_sol(N,K,M);
  printf ("\n");

  return 0;
}

【问题讨论】:

标签: algorithm dynamic-programming


【解决方案1】:

我已经简要地注释了解决方案代码。我没有给出答案,而是将其作为大脑锻炼(良好实践)。如果有什么我没有解释或解释太模糊的地方,我会修改我的注释。

void print_sol(int n,int k,int m)
{
  if (k==0 && m==1) // base case
    return;
  if (k==0 || A[k][m-1] >= n) { // If there are more than n numbers that satisfy A[k][m-1], we can afford to decrease the number of digits in the answer.
    if (!leading_zeros) printf ("0"); // don't print 0's if they lead the number (i.e. 0001 is wrong)
    print_sol(n,k,m-1); // decrement number of digits, m
  } else {
    leading_zeros = 0; // There is no longer a need to consider leading 0's 
                       // because we are gonna print a "1". Notice there's no code "leading_zeros = 0"?
                       // That's because once we've printed the first "1", we don't have to worry about leading 0's.
    printf ("1");
    print_sol(n-A[k][m-1],k-1,m-1); // We just printed a 1-bit, so we only need to print k-1 more 1's.
                                    // Decrement m since we just printed out a digit.
                                    // We initially had to find the n-th number with k 1-bits, but we just took care of a few by printing that "1". Decrease n.
                                    // In basic terms, this line is saying "print out the n-A[k][m-1]-largest number with m-1 digits and k-1 1-bits"
  }
}

【讨论】:

  • 我对编码竞赛一无所知——我的大脑太慢了——但是将短 var 名称换成有意义的名称有优势吗?在教授编程时,我们总是说“使用有意义的变量名”以提高可维护性。编码竞赛有一般例外吗?
  • @halfer 我个人尽量让我的变量名称清晰。有一些有竞争力的程序员只专注于用短变量名快速输出代码(特别是因为这段代码可能不会再被使用)。然而,在这个 Cow ID 问题中,我觉得变量已经足够清楚了。
  • 非常感谢@Felix Hu 的回复,也很好的解释
猜你喜欢
  • 1970-01-01
  • 2019-08-20
  • 2019-02-15
  • 1970-01-01
  • 2022-10-31
  • 2018-03-26
  • 2021-01-14
  • 2013-11-12
  • 1970-01-01
相关资源
最近更新 更多