题目描述
斐波那契单词序列是由比特串(仅包含字符 0 和 1 的字符串)按如下方式定义的:
F(n)={0若 n=01若 n=1F(n−1)+F(n−2)若 n≥2 F(n) = \begin{cases} 0 & \text{若 } n = 0 \\ 1 & \text{若 } n = 1 \\ F(n-1) + F(n-2) & \text{若 } n \geq 2 \end{cases} F(n)=⎩⎨⎧01F(n−1)+F(n−2)若 n=0若 n=1若 n≥2
其中 +++ 表示字符串的拼接。前几个斐波那契单词为:
- F(0)="0"F(0) = \texttt{"0"}F(0)="0"
- F(1)="1"F(1) = \texttt{"1"}F(1)="1"
- F(2)="10"F(2) = \texttt{"10"}F(2)="10"
- F(3)="101"F(3) = \texttt{"101"}F(3)="101"
- F(4)="10110"F(4) = \texttt{"10110"}F(4)="10110"
- F(5)="10110101"F(5) = \texttt{"10110101"}F(5)="10110101"
给定一个比特模式 ppp 和一个整数 nnn,求 ppp 在 F(n)F(n)F(n) 中出现的次数(出现位置可以重叠)。
输入格式
- 第一行:整数 nnn (0≤n≤1000 \leq n \leq 1000≤n≤100)
- 第二行:比特模式 ppp(非空,长度最多 100,000100,000100,000)
输出格式
- 对于每个测试用例,输出
Case i: x,其中 iii 为用例编号,xxx 为出现次数(保证小于 2632^{63}263)
题目分析
问题难点
- 指数级长度增长:斐波那契数列呈指数增长,F(100)F(100)F(100) 的长度远超计算机能直接处理的范围
- 重叠匹配:模式串可能出现重叠,需要准确计数所有出现位置
- 大数处理:结果可能非常大,需要使用
unsigned long long
关键观察
-
递归结构:F(n)=F(n−1)+F(n−2)F(n) = F(n-1) + F(n-2)F(n)=F(n−1)+F(n−2)
-
匹配位置分类:
- 完全在 F(n−1)F(n-1)F(n−1) 中的匹配
- 完全在 F(n−2)F(n-2)F(n−2) 中的匹配
- 跨接在 F(n−1)F(n-1)F(n−1) 和 F(n−2)F(n-2)F(n−2) 连接处的匹配
-
前缀后缀优化:对于跨接匹配,只需要保存每个 F(i)F(i)F(i) 的前 ∣p∣−1|p|-1∣p∣−1 个字符(前缀)和后 ∣p∣−1|p|-1∣p∣−1 个字符(后缀)
解题思路
算法设计
采用记忆化递归 + KMP\texttt{KMP}KMP 字符串匹配:
-
数据结构设计:
struct FibData { ull count; // p在F(n)中的出现次数 string prefix; // F(n)的前min(|p|-1, len)个字符 string suffix; // F(n)的后min(|p|-1, len)个字符 }; -
状态转移:
- count[n]=count[n−1]+count[n−2]+crosscount[n] = count[n-1] + count[n-2] + crosscount[n]=count[n−1]+count[n−2]+cross
- 其中 crosscrosscross 是跨接匹配数,通过检查 suffix[n−1]+prefix[n−2]suffix[n-1] + prefix[n-2]suffix[n−1]+prefix[n−2] 计算
-
KMP\texttt{KMP}KMP 算法:用于高效计算字符串匹配次数,时间复杂度 O(n+m)O(n+m)O(n+m)
具体步骤
- 预处理:计算模式串 ppp 的 LPS\texttt{LPS}LPS(最长前缀后缀)数组
- 基础情况:
- n=0n = 0n=0:直接计算
"0"中的匹配 - n=1n = 1n=1:直接计算
"1"中的匹配
- n=0n = 0n=0:直接计算
- 递归计算:
- 先计算 F(n−1)F(n-1)F(n−1) 和 F(n−2)F(n-2)F(n−2) 的匹配信息
- 计算跨接匹配:拼接 suffix[n−1]suffix[n-1]suffix[n−1] 和 prefix[n−2]prefix[n-2]prefix[n−2],使用 KMP\texttt{KMP}KMP 统计匹配数
- 更新 prefix[n]prefix[n]prefix[n] 和 suffix[n]suffix[n]suffix[n](长度不超过 ∣p∣−1|p|-1∣p∣−1)
- 输出结果:count[n]count[n]count[n] 即为所求
时间复杂度
- KMP\texttt{KMP}KMP 匹配:O(L)O(L)O(L),其中 LLL 为文本长度
- 递归计算:O(n)O(n)O(n) 个状态
- 总复杂度:O(n×∣p∣)O(n \times |p|)O(n×∣p∣),在约束范围内可接受
空间复杂度
- O(n×∣p∣)O(n \times |p|)O(n×∣p∣),主要存储前缀后缀信息
代码实现
// Fibonacci Words
// UVa ID: 1282
// Verdict: Accepted
// Submission Date: 2025-11-19
// UVa Run Time: 0.050s
//
// 版权所有(C)2025,邱秋。metaphysis # yeah dot net
#include <bits/stdc++.h>
using namespace std;
typedef unsigned long long ull;
const int MAXN = 105;
struct FibData {
ull count;
string prefix, suffix;
};
FibData dp[MAXN];
string pattern;
int patternLen;
vector<int> lps;
void computeLPS() {
lps.resize(patternLen);
lps[0] = 0;
int len = 0, i = 1;
while (i < patternLen) {
if (pattern[i] == pattern[len]) {
lps[i++] = ++len;
} else {
if (len != 0) len = lps[len - 1];
else lps[i++] = 0;
}
}
}
ull kmpCount(const string &text) {
int n = text.size();
if (n < patternLen) return 0;
ull res = 0;
int i = 0, j = 0;
while (i < n) {
if (pattern[j] == text[i]) { i++; j++; }
if (j == patternLen) { res++; j = lps[j - 1]; }
else if (i < n && pattern[j] != text[i]) {
if (j != 0) j = lps[j - 1];
else i++;
}
}
return res;
}
void solve(int n) {
if (dp[n].count != -1) return;
if (n == 0) {
dp[n].count = kmpCount("0");
dp[n].prefix = dp[n].suffix = "0";
return;
}
if (n == 1) {
dp[n].count = kmpCount("1");
dp[n].prefix = dp[n].suffix = "1";
return;
}
solve(n - 1);
solve(n - 2);
string suffixPart = dp[n - 1].suffix;
string prefixPart = dp[n - 2].prefix;
if (suffixPart.size() > patternLen - 1) suffixPart = suffixPart.substr(suffixPart.size() - (patternLen - 1));
if (prefixPart.size() > patternLen - 1) prefixPart = prefixPart.substr(0, patternLen - 1);
ull cross = (suffixPart + prefixPart).size() >= patternLen ? kmpCount(suffixPart + prefixPart) : 0;
dp[n].count = dp[n - 1].count + dp[n - 2].count + cross;
dp[n].prefix = dp[n - 1].prefix;
if (dp[n].prefix.size() < patternLen - 1) {
string temp = dp[n - 1].prefix + dp[n - 2].prefix;
dp[n].prefix = temp.size() > patternLen - 1 ? temp.substr(0, patternLen - 1) : temp;
}
dp[n].suffix = dp[n - 2].suffix;
if (dp[n].suffix.size() < patternLen - 1) {
string temp = dp[n - 1].suffix + dp[n - 2].suffix;
dp[n].suffix = temp.size() > patternLen - 1 ? temp.substr(temp.size() - (patternLen - 1)) : temp;
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
int n, caseNo = 1;
while (cin >> n) {
cin >> pattern;
patternLen = pattern.size();
computeLPS();
for (int i = 0; i < MAXN; i++) dp[i].count = -1;
solve(n);
cout << "Case " << caseNo++ << ": " << dp[n].count << "\n";
}
return 0;
}
总结
本题通过结合动态规划的思想和 KMP\texttt{KMP}KMP 字符串匹配算法,巧妙地解决了斐波那契单词中模式匹配的计数问题。关键在于利用斐波那契数列的递归性质,避免直接构造巨大的字符串,而是通过保存必要的前缀后缀信息来计算跨接匹配。这种"分治+合并"的思路在处理具有递归结构的问题时非常有效。

5618

被折叠的 条评论
为什么被折叠?



