【问题标题】:Dynamic programming to solve the fibwords problem动态规划解决fibwords问题
【发布时间】:2021-02-25 16:45:25
【问题描述】:

问题陈述:位串的斐波那契字序列定义为:
F(0) = 0, F(1) = 1
F(n - 1) + F(n - 2) 如果 n ≥ 2
例如:F(2) = F(1) + F(0) = 10,F(3) = F(2) + F(1) = 101,等等。
给定一个位模式 p 和一个数字 n,p 在 F(n) 中出现的频率是多少?
输入: 每个测试用例的第一行包含整数 n (0 ≤ n ≤ 100)。第二行包含位 模式 p。模式 p 是非空的,长度最多为 100 000 个字符。
输出: 对于每个测试用例,显示其用例编号,后跟位模式 p 的出现次数 F(n)。事件可能重叠。出现次数将少于 2^63。
样本输入
6
10
样本输出:
案例1:5
根据我在互联网上找到的提示,我实现了一个分而治之的算法来解决这个问题:我们可以认为从 F(n-1) 到 F(n) 的过程为字符串替换规则:每个 '1' 变成 '10' 而 '0' 变成 '1'。这是我的代码:

#include <string>
#include <iostream>
using namespace std;
#define LL long long int
LL count = 0;
string F[40];
void find(LL n, char ch1,char ch2 ){//Find occurences of eiher "11" / "01" / "10" in F[n]
    LL n1 = F[n].length();
    for (int i = 0;i+1 <n1;++i){
        if (F[n].at(i)==ch1&&F[n].at(i+1)==ch2) ++ count;   
    }
}
void find(char ch, LL n){
    LL n1 = F[n].length();
    for (int i = 0;i<n1;++i){
        if (F[n].at(i)==ch) ++count;
    }
}
void solve(string p, LL n){//Recursion
    // cout << p << endl;
    LL n1 = p.length();
    if (n<=1&&n1>=2) return;//return if string pattern p's size is larger than F(n) 
    //When p's size is reduced to 2 or 1, it's small enough now that we can search for p directly in F(n)
    if (n1<=2){
        if (n1 == 2){
            if (p=="00") return;//Return since there can't be two subsequent '0' in F(n) for any n
            else find(n,p.at(0),p.at(1));
            return;
        }
        if (n1 == 1){
            if (p=="1") find('1',n);
            else find('0',n);
            return;
        }
    }
    string p1, p2;//if the last character in p is 1, we can replace it with either '1' or '0'
                  //p1 stores the substring ending in '1' and p2 stores the substring ending in '0' 
    for (LL i = 0;i<n1;++i){//We replace every "10" with 1, "1" with 0.
        if (p[i]=='1'){
            if (p[i+1]=='0'&&(i+1)!= n1){
                if (p[i+2]=='0'&&(i+2)!= n1) return;//Return if there are two subsequent '0'
                p1.append("1");//Replace "10" with "1"
                ++i;
            }
            else {
            p1.append("0");//Replace "1" with "0"
            }
        }
        else {
            if (p[i+1]=='0'&&(i+1)!= n1){//Return if there are two subsequent '0'
                return;
            }
            p1.append("1");
        }
    }
    solve(p1,n-1);
    if (p[n1-1]=='1'){
        p2 = p1;
        p2.back() = '1';
        solve(p2,n-1);
    }
}
main(){
    F[0] = "0";F[1] = "1";
    for (int i = 2;i<38;++i){
        F[i].append(F[i-1]);
        F[i].append(F[i-2]);
    }//precalculate F(0) to F(37)
    LL t = 0;//NumofTestcases
    int n; string p;
    while (cin >> n >> p) {
        count = 0;
        solve(p,n);
        cout << "Case " << ++t << ": " << count << endl;
    }
}

上述程序运行良好,但只有少量输入。当我将上述程序提交给 codeforces 时,我得到了一个错误的答案,因为尽管我缩短了模式字符串 p 并将 n 减少到 n',但 F[n'] 的大小仍然非常大(n'>=50)。我如何修改我的代码以使其在这种情况下工作,或者是否有另一种方法(例如动态编程?)。非常感谢您的任何建议。

【问题讨论】:

    标签: recursion dynamic-programming fibonacci divide-and-conquer


    【解决方案1】:

    我现在没有时间尝试自己编写代码,但我有一个建议的方法。

    首先,我应该指出,虽然您使用的提示肯定是准确的,但我看不出有任何直接的方法可以解决问题。也许正确的后续行动会比我建议的更简单。

    我的做法:

    • 找到前两个ns 使得length(F(n)) >= length(pattern)。计算这些是一个简单的递归。重要的见解是,每个后续值都将以这两个值之一开始,也将以其中一个结束。 (对于所有相邻的值都是如此——对于任何m &gt; nF(m) 都将以F(n)F(n - 1) 开头。不难看出原因。)

    • 计算并缓存这两个Fs 中模式的出现次数,但无论是什么索引移位技术都有意义。

    • 对于F(n+1)(以及所有后续值)通过相加计算

      • F(n) 的计数
      • F(n - 1) 的计数
      • 跨越F(n)F(n - 1) 的计数。我们可以通过将pattern 的每个细分测试为(非空)prefixsuffix 值(即,在每个内部索引处拆分)并计算 F(n) 以 prefixF(n - 1) 结尾的那些来实现这一点以suffix 开头。但是我们不必拥有所有F(n)F(n - 1) 来执行此操作。我们只需要模式长度的F(n) 的尾部和F(n - 1) 的头部。所以我们不需要计算所有的F(n)。我们只需要知道我们当前的初始值以这两个初始值中的哪一个结束。但起点永远是前任,终点在前两者之间摇摆不定。应该很容易跟踪。

    那么时间复杂度应该与n 和模式长度的乘积成正比。

    如果我明天有时间,我会看看我是否可以编写代码。但它不会出现在 C 中——那些年一去不复返了。

    可以提前一次收集prefix/suffix 对的列表

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-10-24
      • 1970-01-01
      • 2016-10-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多