Algorithm 计算n位字符串的数量,以便在字符串的每个前缀中,零的数量至少是1的k倍

Algorithm 计算n位字符串的数量,以便在字符串的每个前缀中,零的数量至少是1的k倍,algorithm,permutation,combinatorics,Algorithm,Permutation,Combinatorics,我们如何计算n位字符串的数量,以便在字符串的每个前缀中,零的数量至少是1的k倍 示例: 1当n=5和k=3时,有3个这样的字符串:00000、00001和00010。在每个字符串中,每个初始子字符串的零数至少是1的三倍 2当n=6和k=2时,有8个这样的字符串:000000、000001、000010、000011、000100、000101001000和001001 我实现了递归计数,但我需要一个比这更有效的算法或公式。如果有人能在输出中找到一种模式来避免浪费在递归上的时间,那就太好了 为了澄

我们如何计算n位字符串的数量,以便在字符串的每个前缀中,零的数量至少是1的k倍

示例:

1当n=5和k=3时,有3个这样的字符串:00000、00001和00010。在每个字符串中,每个初始子字符串的零数至少是1的三倍

2当n=6和k=2时,有8个这样的字符串:000000、000001、000010、000011、000100、000101001000和001001

我实现了递归计数,但我需要一个比这更有效的算法或公式。如果有人能在输出中找到一种模式来避免浪费在递归上的时间,那就太好了

为了澄清问题,我将陈述一些限制条件,
1通过构造,而不是反复试验来找到合法的字符串

从左侧开始为字符串位置编号,从1开始。为了便于记法,设j=k+1。第一个1可以在位置j之前出现;第二个不早于位置2j,依此类推

例如,对于6,2,j=3的情况。第一个1可以出现在位置3的任何位置;第二个只能出现在位置6

编写一个DP函数来完成此决策过程。现在不要再生成字符串了,我们只需要所有数组,其中每个元素a[i]都不小于i*j。对于上述情况,第一个决策点是初始元素。选择包括

[]
[3]
[4]
[5]
[6]
第一个和最后一个是终端搜索;其他元素被扩展为可能的第二个元素,屈服

[3, 6]
[4, 6]
[5, 6]
。。。这些很容易转化为八种理想的解决方案


你能从那里实现递归吗?

虽然可能有一个涉及组合分析的更复杂的解决方案,但生成一个动态规划解决方案是非常简单的,它在时间上工作,需要额外的空间。因为这个解决方案的内环只涉及一个加法,所以在n变大之前它是相当快的,尽管除了最小的问题之外,所有问题都需要多精度算法

动态规划涉及将递归从头开始;在动态规划中,我们不是从目标问题开始,递归地将其分解为更简单的问题,而是以适当的顺序解决所有更简单的问题,这样我们就可以在需要之前得到中间结果。当相同的简单问题在递归过程中多次出现时,它是有效的,这与记忆递归的用例相同。不过,DP可能比记忆更有效,因为中间计算的正确顺序通常意味着在任何给定时间都需要记住其值的中间计算数量显著减少。它还避免了检查所需中间值是否已计算的开销,尽管这通常并不重要

在本例中,我们将使用一个非常简单的递归,它基于以下事实:任何长度为m的有效二进制序列都是长度为m-1,后跟0或1的有效二进制序列。在有效二进制序列的末尾加1并不总是可能的,因为这可能违反计数约束。但是总是可以删除一个1,它恰好位于有效二进制序列的末尾

我们实际上要计算的是长度为n,正好为m的有效序列的数量。然后,长度为n的有效序列的数量可以通过将不同m值的所有计数相加得到。我们可以区分三种情况:

m太大;有太多的1,所以不可能构造一个1的数目至少是0的k倍的序列。检查这种情况很容易;零的数量是n-m,因为所有不是1的东西都必须是零,所以如果m>kn-m,m就太大了。一点代数将表明这与m>n/k+1是一样的

m是零。只有一个长度为n且没有1的有效序列:n个零的序列。不管k的值是多少,这个序列都是有效的

对于每一个大于0且小于上述第1点截止值的m值,我们可以通过考虑最后两个可能的值来计算有效序列的数量:一个有效序列必须是长度为n-1且m-1的有效序列,然后是另一个有效序列,或长度为n-1的有效序列,其中m个1后跟一个0

综合所有这些,我们可以递归计算:

Count[n, m] = 1 if m = 0 = Count[n-1, m-1] + Count[n-1, m] if 0 < m ≤ n / (k + 1) = 0 if m > n / (k + 1) 现在,让我们将递归转化为动态编程解决方案

很明显,给定n的Count[n,…]的所有值仅依赖于Count[n-1,…]的值。因此,如果我们计算Count[n-1,…]的所有值,我们可以使用它们来产生值的向量 对于Count[n,…],在此之后,我们不再需要记住Count[n-1,…]的任何值。所以我们只需要保持两个向量的长度为1+n/k+1。但我们可以做得更好,因为Count[n,m]的值只取决于Count[n-1,m]和Count[n-1,m-1]。这一事实允许我们使用单个向量进行计算,前提是我们从最大有效值m开始向后工作

因此,计算长度为n的有效位序列的最终解决方案,其中每个前缀至少有k个零:

创建长度为1+n/k+1的向量计数。将计数[0]设置为1,并将每隔一个元素设置为0

对于从1到n(含)的每个i:

对于从i/k+1到1(含)的每个j:

将计数[j-1]添加到计数[j] 返回计数中所有值的总和


根据上面给出的建议,我认为这就是您想要的。

评论不适用于扩展讨论;这段对话已经结束了。我将你原来的标题和文本中的“x”改为“k”,以便更清楚地说明这是问题的一个参数,避免任何人将其与待解决的未知问题混淆。最好保持符号的一致性,或者在后续编辑中使用k,或者将所有文本和代码改回使用x。任何不符合要求的字符串都必须有一个前缀,前缀正好是floorm/k+1+1,其中m是前缀的长度。我想知道,用一些简单的组合数学来计算这样的字符串,并使用包含排除原理,是否能给出比递归更好的求和。@eric:也许吧,但有一个简单的DP解决方案,它在二次时间和线性空间中运行,在内部循环中只使用一个加法。@rici:你能解释一下DP解决方案吗? Count[n, m] = 1 if m = 0 = Count[n-1, m-1] + Count[n-1, m] if 0 < m ≤ n / (k + 1) = 0 if m > n / (k + 1)
    // Trivial recursive solution
    // It appears that one on the recursive calls
    // can be removed as tail recursion
    // Appending multiple '0's, just until another '1' is possible,
    // seems to be a nice optimisation, but will complicate the
    // final logic (at the end-of string) and will need a few
    // extra conditions.

#include <stdio.h>

char buff[100];

unsigned recurse(unsigned zeros, unsigned ones, unsigned x, unsigned len)
{
unsigned count=0;
unsigned pos = zeros+ones;

if (pos == len) {
        buff[pos] =0;
        printf("%s\n", buff);
        return 1;
        }

buff[pos] = '0';
count += recurse(zeros+1, ones, x, len);
        // if no '1' is possible yet,
        // we could loop here, adding more '0' bits
        // , but we must take care not to exceed len
        // and to maintain the count correctly
        // (and possibly yield the resulting string)

if (ones < zeros /x) {
        buff[pos] = '1';
        count += recurse(zeros, ones+1, x, len);
        }

return count; // number of solutions, given the prefix [0..pos]
}

int main(int argc, char **argv)
{
unsigned x,n, result;
sscanf(argv[1], "%u", &n);
sscanf(argv[2], "%u", &x);

result = recurse(0,0, x, n);
printf("Result=%u\n", result);
return 0;
}
#include <iostream>
using namespace std;

int main()
{
    int t,i,j,m,n,k,sum;
    
    sum=0;
    
    cin>>n>>k;
    m=n/(k+1);
    
    int count[m+1]={0};
    count[0]=1;
    
    for(i=k+1;i<=n;i++)
    {
        for(j=i/(k+1);j>=1;j--)
        {
            count[j]=count[j]+count[j-1];
        }
    }
    
    for(i=0;i<m+1;i++)
    {
        sum=sum+count[i];
    }
    
    cout<<sum<<endl;
    
    return 0;
}