Search for a command to run...
Progressive hints first, then the full explanation and implementation when you're ready to cash out.
Review status
AI-generated and still unreviewed. Double-check the details before internalizing them.
Hints
Open only as much as you need to keep the solve alive.
Start with the obvious cut-DP. If is the number of valid ways to cut the first characters, then So the whole problem is really: for each ending position, which starting positions make the last piece a Fibonacci string?
For , every Fibonacci string is a prefix of one infinite word (the limit of the Fibonacci strings). The only annoying outsider is .
So a segment can become a forbidden string only if, as you extend it to the right, it keeps matching a prefix of the whole time.
Think about one fixed cut position . While you scan more characters, the substring starting at is either still equal to some prefix of , or it has already mismatched. Once it mismatches, it's dead forever — no longer extension can magically become a Fibonacci string later. That means you do not need all old values, only the ones that are still “alive”.
Maintain all alive states as pairs , where the current suffix of length equals , and is the weight of the cut before that suffix.
After reading a new character :
The killer fact is this: the alive lengths are exactly the border chain of some prefix of the Fibonacci word. For Fibonacci words, that chain has only elements (equivalently, the failure chain is logarithmic).
So each character can be processed by scanning only this tiny list. Then where Precompute the prefix of the infinite Fibonacci word up to length , and you're done. Pretty evil, but clean.
Let be the current concatenated string, and let be the number of valid ways to cut the first characters of .
Of course, For , choose the start of the last piece at position . Then
If we define then this becomes where is the total weight of cuts whose last piece is forbidden.
So far, so standard.
If the memory limit were sane, we'd just store all and test all Fibonacci lengths. But with 4 MB, storing an array of size is dead on arrival. So we need to remember only the old values that can still matter later.
For , the Fibonacci strings are and every is a prefix of the infinite Fibonacci word (the limit of the recursion ).
The only forbidden string not on this chain is That one is a length- nuisance; we'll special-case it.
Now fix a cut position . As we scan the text further right, the substring starting at can only become some if it keeps matching a prefix of at every intermediate length.
Once it mismatches, it's over. That cut position is useless forever.
This is the key reason we do not need the whole array.
After processing some prefix of length , call a cut position alive if the suffix is equal to a prefix of .
If its length is , then the only data we need from that cut is exactly So we store alive states as pairs
There is at most one alive state for each length , because the starting position is then fixed.
Also, all alive lengths are suffixes of the current string which are prefixes of . Equivalently, they are exactly the border chain of the longest matched prefix of .
Suppose we already processed characters, and we read a new character .
How do alive states change?
A fresh cut before this character creates a length- piece.
Any old alive state survives iff the next character of matches: Then it becomes
That's it. No hidden nonsense.
So after reading one character, we can build the new alive list by just scanning the old one.
badAfter constructing the alive states for the new position, which of them correspond to forbidden last pieces?
Exactly those whose length is a Fibonacci-string length for : Because all those lengths correspond to prefixes of .
And we must also add the special case , i.e. the fresh length- piece when the current character is .
So if the new position is ,
+ \sum_{(\ell,w)\in alive_i\atop \ell\in\{1,2,3,5,8,\dots\}} w.$$ Then simply $$dp[i]=pref[i-1]-bad[i] \pmod{998244353}.$$ ## Why is the alive list small? This is the one combinatorics lemma you need to swallow. A classical fact about the Fibonacci word is: > Every prefix of $F$ has a failure/border chain of length $O(\log N)$. One way to phrase it: prefixes of the Fibonacci word are standard / semi-standard Fibonacci words, their shortest periods are Fibonacci lengths, and when you jump to the longest proper border, that shortest period strictly decreases. Since there are only $O(\log N)$ Fibonacci lengths up to $N$, the whole chain is logarithmic. Our alive lengths are exactly such a border chain. Therefore the number of alive states is always $$O(\log N).$$ So each character is processed in $O(\log N)$ time. ## Precomputing the infinite Fibonacci word We need fast access to $F[t]$ for $t\le 3\cdot 10^6$. Just build the first $3\cdot 10^6+1$ characters of $F$ once. That's only about **3 MB**, which fits the stupidly tiny memory limit. The clean way is recursive generation from the definition of Fibonacci strings: - $f_0=\texttt{0}$, - $f_1=\texttt{1}$, - $f_k=f_{k-1}+f_{k-2}$. Pick some large $k$ with $|f_k|\ge 3\cdot 10^6+1$, and recursively output only the needed prefix. ## Correctness sketch We maintain the invariant: > After processing $pos$ characters, the stored alive list consists exactly of all lengths $\ell$ such that the suffix of length $\ell$ equals $F[1..\ell]$, and its stored weight is $dp[pos-\ell]$. This is true initially (empty list). When we append one character $c$: - any new alive suffix of length $1$ is possible iff $c=\texttt{1}$, with weight $dp[pos]$; - any new alive suffix of length $\ell+1$ must come from a previous alive suffix of length $\ell$, and it survives iff the next needed Fibonacci-word character equals $c$. So the transition builds exactly the new alive list. Then `bad` counts exactly those cuts whose last piece is one of the forbidden Fibonacci strings: - $\texttt{0}$ is handled separately, - all other forbidden strings are precisely the alive states whose lengths are Fibonacci lengths $1,2,3,5,\dots$. Therefore the recurrence computes the exact number of valid cuts. ## Complexity Let $$M=\sum_{i=1}^{n} |s_i|.$$ - Precomputing the needed prefix of $F$: $O(M_{\max})$ where $M_{\max}=3\cdot 10^6+1$. - Processing each character: $O(\#alive)=O(\log M)$. So total time is $$O(M\log M).$$ Memory usage is $$O(M_{\max})$$ for the stored prefix of the Fibonacci word, plus a tiny $O(\log M)$ alive list. In actual bytes: about **3 MB** for the word itself, which is exactly the kind of cursed engineering this problem wanted.#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
const int MOD = 998244353;
const int MAXLEN = 3000000 + 1;
vector<int> fibSizeAll; // lengths with f0=0, f1=1
string fibWord; // prefix of the infinite Fibonacci word F = 1011010...
int fibPtr = 0;
void buildFibWord(int k, int need) {
if (need == 0) return;
if (k == 0) {
fibWord[fibPtr++] = '0';
return;
}
if (k == 1) {
fibWord[fibPtr++] = '1';
return;
}
if (need <= fibSizeAll[k - 1]) {
buildFibWord(k - 1, need);
} else {
buildFibWord(k - 1, fibSizeAll[k - 1]);
buildFibWord(k - 2, need - fibSizeAll[k - 1]);
}
}
inline void addmod(int &a, int b) {
a += b;
if (a >= MOD) a -= MOD;
}
int main() { setIO();
// Precompute Fibonacci-string lengths for generation.
fibSizeAll = {1, 1};
while (fibSizeAll.back() < MAXLEN) {
int m = (int)fibSizeAll.size();
fibSizeAll.push_back(fibSizeAll[m - 1] + fibSizeAll[m - 2]);
}
// Build the first MAXLEN characters of the infinite Fibonacci word.
fibWord.resize(MAXLEN);
int k = 0;
while (fibSizeAll[k] < MAXLEN) ++k;
buildFibWord(k, MAXLEN);
// Distinct forbidden lengths for f_k, k >= 1: 1, 2, 3, 5, 8, ...
vector<int> forbidLen = {1, 2};
while (true) {
ll nxt = 1LL * forbidLen[(int)forbidLen.size() - 1] + forbidLen[(int)forbidLen.size() - 2];
if (nxt > MAXLEN) break;
forbidLen.push_back((int)nxt);
}
int n;
cin >> n;
// alive = pairs (len, value), meaning:
// current suffix of length len equals F[1..len], and value = dp[pos-len].
vector<pair<int, int>> alive, nxtAlive;
alive.reserve(128);
nxtAlive.reserve(128);
int last_dp = 1; // dp[0]
int pref = 1; // sum dp[0..current_pos]
for (int qi = 0; qi < n; ++qi) {
string s;
cin >> s;
for (char c : s) {
nxtAlive.clear();
// Fresh start at this character: only '1' can continue along Fibonacci word.
if (c == '1') nxtAlive.push_back({1, last_dp});
// Extend all previously alive starts if the next Fibonacci-word character matches.
for (auto [len, val] : alive) {
if (fibWord[len] == c) {
nxtAlive.push_back({len + 1, val});
}
}
// bad = ways where the last piece is forbidden.
// Special-case f0 = "0".
int bad = (c == '0' ? last_dp : 0);
// All other forbidden strings are prefixes of F with Fibonacci lengths 1,2,3,5,...
int p = 0;
for (auto [len, val] : nxtAlive) {
while (p < (int)forbidLen.size() && forbidLen[p] < len) ++p;
if (p < (int)forbidLen.size() && forbidLen[p] == len) {
addmod(bad, val);
}
}
int cur = pref - bad;
if (cur < 0) cur += MOD;
addmod(pref, cur);
last_dp = cur;
alive.swap(nxtAlive);
}
cout << last_dp << '\n';
}
}#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
const int MOD = 998244353;
const int MAXLEN = 3000000 + 1;
vector<int> fibSizeAll; // lengths with f0=0, f1=1
string fibWord; // prefix of the infinite Fibonacci word F = 1011010...
int fibPtr = 0;
void buildFibWord(int k, int need) {
if (need == 0) return;
if (k == 0) {
fibWord[fibPtr++] = '0';
return;
}
if (k == 1) {
fibWord[fibPtr++] = '1';
return;
}
if (need <= fibSizeAll[k - 1]) {
buildFibWord(k - 1, need);
} else {
buildFibWord(k - 1, fibSizeAll[k - 1]);
buildFibWord(k - 2, need - fibSizeAll[k - 1]);
}
}
inline void addmod(int &a, int b) {
a += b;
if (a >= MOD) a -= MOD;
}
int main() { setIO();
// Precompute Fibonacci-string lengths for generation.
fibSizeAll = {1, 1};
while (fibSizeAll.back() < MAXLEN) {
int m = (int)fibSizeAll.size();
fibSizeAll.push_back(fibSizeAll[m - 1] + fibSizeAll[m - 2]);
}
// Build the first MAXLEN characters of the infinite Fibonacci word.
fibWord.resize(MAXLEN);
int k = 0;
while (fibSizeAll[k] < MAXLEN) ++k;
buildFibWord(k, MAXLEN);
// Distinct forbidden lengths for f_k, k >= 1: 1, 2, 3, 5, 8, ...
vector<int> forbidLen = {1, 2};
while (true) {
ll nxt = 1LL * forbidLen[(int)forbidLen.size() - 1] + forbidLen[(int)forbidLen.size() - 2];
if (nxt > MAXLEN) break;
forbidLen.push_back((int)nxt);
}
int n;
cin >> n;
// alive = pairs (len, value), meaning:
// current suffix of length len equals F[1..len], and value = dp[pos-len].
vector<pair<int, int>> alive, nxtAlive;
alive.reserve(128);
nxtAlive.reserve(128);
int last_dp = 1; // dp[0]
int pref = 1; // sum dp[0..current_pos]
for (int qi = 0; qi < n; ++qi) {
string s;
cin >> s;
for (char c : s) {
nxtAlive.clear();
// Fresh start at this character: only '1' can continue along Fibonacci word.
if (c == '1') nxtAlive.push_back({1, last_dp});
// Extend all previously alive starts if the next Fibonacci-word character matches.
for (auto [len, val] : alive) {
if (fibWord[len] == c) {
nxtAlive.push_back({len + 1, val});
}
}
// bad = ways where the last piece is forbidden.
// Special-case f0 = "0".
int bad = (c == '0' ? last_dp : 0);
// All other forbidden strings are prefixes of F with Fibonacci lengths 1,2,3,5,...
int p = 0;
for (auto [len, val] : nxtAlive) {
while (p < (int)forbidLen.size() && forbidLen[p] < len) ++p;
if (p < (int)forbidLen.size() && forbidLen[p] == len) {
addmod(bad, val);
}
}
int cur = pref - bad;
if (cur < 0) cur += MOD;
addmod(pref, cur);
last_dp = cur;
alive.swap(nxtAlive);
}
cout << last_dp << '\n';
}
}