題目傳送門:LOJ #3185。
題意簡述:
題目說得很清楚了。
題解:
首先需要了解「斐波那契數系」為何物。
按照題目中定義的斐波那契數列 \(F_n\),可以證明,每個非負整數 \(n\) 都能夠以唯一方式用如下方式描述:
其中 \(m\) 是正整數,\(a\) 是長度為 \(m\) 的 \(01\) 序列,\(a\) 中不存在相鄰兩項 \(a_i\) 與 \(a_{i+1}\) 同為 \(1\)。
例如,當 \(m=5\) 時,有:
對斐波那契數系更詳細的解釋可以在《具體數學》(人民郵電出版社)的第 248 頁找到。
將某個正整數 \(n\) 以這種方式表示后,可以方便地計算 \(X(n)\) 的值:
-
可以發現一個 \(1\) 可以變成低位的兩個 \(1\),事實上,形如 \(\overline{1000\cdots000}\) 的數的 \(X\) 函數值應為 \(1\) 的位置加 \(1\) 除以 \(2\) 向下取整。
例如 \(X(\overline{10000000})=\left\lfloor\frac{9}{2}\right\rfloor=4\),即 \(X(34)=4\)。 -
進一步地,假設 \(a\) 中為 \(1\) 的位置依次為 \(b_1,b_2,\ldots,b_k\),並且令 \(b_0=0\)。
-
令 \(f_i\) 為只考慮前 \(i\) 個 \(1\),且不將第 \(i\) 個 \(1\)(即位置 \(b_i\) 上的 \(1\))變成低位的兩個 \(1\) 時的方案數(此時最高位為 \(b_i\))。
令 \(g_i\) 為只考慮前 \(i\) 個 \(1\),且將第 \(i\) 個 \(1\)(即位置 \(b_i\) 上的 \(1\))變成低位的兩個 \(1\) 時的方案數(此時最高位為 \(b_i-1\))。 -
經過觀察(打表)並綜合上面的結論,可以得出:
\(f_i=f_{i-1}+g_{i-1},g_i=\left\lfloor\frac{b_i-b_{i-1}-1}{2}\right\rfloor\cdot f_{i-1}+\left\lfloor\frac{b_i-b_{i-1}}{2}\right\rfloor\cdot g_{i-1}\)。 -
邊界條件是 \(f_0=1,g_0=0\)。
答案為 \(f_k+g_k\)。
令 \(b\) 的差分數列為 \(d\),即 \(d_i=b_i-b_{i-1}\)。
則可以簡單地將轉移寫成矩陣的形式:
那么,只要能夠維護 \(d_i\) 及其對應的矩陣乘積序列,就可以求得答案。
因為有類似在中間插入的操作,可以考慮使用平衡樹維護。
在下文的代碼中,我使用了無旋 Treap 來維護矩陣序列。
接下來考慮某個插入某個 \(F_i\) 的操作會對 \(d\) 產生何種影響:
一、如果 \(a_{i-1},a_i,a_{i+1}\) 均為 \(0\),即加入 \(F_i\) 不影響 \(a\) 的合法性:
那么就直接加入 \(F_i\),令 \(a_i=1\),並相對應地修改 \(d\) 數列。
二、否則會影響 \(a\) 的合法性,經過細致的分類討論與歸納(找規律)后:
發現插入時不同的特征,會導致三類不同的結果,而且與形如 \(\overline{10101\cdots101}\) 的極大的 \(01\) 交替的子串有密切關系,如圖所示:

原序列 \(a\) 中一段長度為 \(13\) 的極大的 \(01\) 交替子串(有 \(7\) 個 \(1\))被截取了下來(左側是高位,右側是低位);
其中 \(0\) 所在的位置被染成藍色,\(1\) 所在的位置被染成黃色,新加入的 \(1\) 被染成綠色。
具體推導過程這里不再詳述,讀者可以反復使用如下兩個等式,以對結論進行驗證:
主要有三種本質不同的情況:
(為了表示方便,令 \(\mathrm{high}\) 為交替子串的最高位,\(\mathrm{low}\) 為交替子串的最低位,\(\mathrm{pos}\) 為新加入的 \(1\) 的位置)
(顯然有 \(\mathrm{low}\equiv\mathrm{high}\pmod{2}\) 和 \(\mathrm{low}-1\le\mathrm{pos}\le\mathrm{high}+1\))
-
\(\mathrm{pos}=\mathrm{high}+1\):
修改 \(a_{\mathrm{high}}\) 為 \(0\),修改 \(a_{\mathrm{high}+2}\) 為 \(1\),並相對應地修改 \(d\) 數列。
注意這里的修改 \(a_{\mathrm{high}+2}=1\) 可能造成連鎖反應,需要「遞歸」處理(稍后將揭示遞歸只會重復常數層)。 -
\(\mathrm{low}-1\le\mathrm{pos}\le\mathrm{high}\) 且 \(\mathrm{pos}\not\equiv\mathrm{high}\pmod{2}\):
修改 \(a_{\mathrm{pos}+1},a_{\mathrm{pos}+3},\ldots,a_{\mathrm{high}}\) 為 \(0\),修改 \(a_{\mathrm{high}+1}\) 為 \(1\),並相對應地修改 \(d\) 數列。 -
\(\mathrm{low}-1\le\mathrm{pos}\le\mathrm{high}\) 且 \(\mathrm{pos}\equiv\mathrm{high}\pmod{2}\):
修改 \(a_{\mathrm{pos}},a_{\mathrm{pos}+2},\ldots,a_{\mathrm{high}}\) 為 \(0\),修改 \(a_{\mathrm{high}+1}\) 為 \(1\);
修改 \(a_{\mathrm{pos}-2},a_{\mathrm{pos}-4},\ldots,a_{\mathrm{low}}\) 為 \(0\),修改 \(a_{\mathrm{pos}-1},a_{\mathrm{pos}-3},\ldots,a_{\mathrm{low}+1}\) 為 \(1\);
最后修改 \(a_{\mathrm{low}-2}\) 為 \(1\),並相對應地修改 \(d\) 數列。
注意此處涉及的修改比較復雜,但是可以發現區間 \([\mathrm{low},\mathrm{pos}-1]\) 的修改可以看作是向高位整體移動了 \(1\) 位,對 \(d\) 的影響不大。
注意這里的修改 \(a_{\mathrm{low}-2}=1\) 可能造成連鎖反應,需要「遞歸」處理(稍后將揭示遞歸只會重復常數層)。
先不考慮上文提到的「遞歸」,分析一下上述三種情況對 \(d\) 數列的改變。
可以發現形式均為:刪除一段 \(d\) 中的元素,並加入(或修改)常數個 \(d\) 中的元素。
這兩種操作都是可以用無旋 Treap 維護的,且因為每次加入(或修改)僅常數個元素,所以這部分的時間復雜度為期望 \(\mathcal{O}(n\log n)\)。
考慮到可能發生連鎖反應,實際上這並不需要擔心,稍加分析就可發現:
第一種情況造成的連鎖反應只可能觸發第二種情況,而第二種情況是不會發生連鎖反應的。
第三種情況造成的連鎖反應只可能觸發第一種情況,而因為 \(a_{\mathrm{low}}\) 變為了 \(0\),所以不會觸發進一步的連鎖反應。
上面的分析中還涉及到一個問題:如何提取被新加入的 \(F_i\) 影響的 \(01\) 交替子串?
比較方便的是使用 std::set< structure > 維護,其中 structure 是自定義的結構體,具有存儲一段極大 \(01\) 交替子串的功能。
在做上述修改的時候,不難發現對極大 \(01\) 交替子串的影響也是 \(\mathcal{O}(1)\) 的,故這部分也可以在 \(\mathcal{O}(n\log n)\) 內實現。
要格外注意實現細節,包括刪除子串的一部分,合並新出現的相鄰的極大 \(01\) 交替子串等。
令人欣喜的是,本題中需要合並相鄰的交替子串的情況,僅可能由新加入單個 \(1\) 導致。
這是因為「情況三」中,整段的連續子串的移動是「向內」的。
下面是代碼,如上文所述,時間復雜度為期望 \(\mathcal{O}(n\log n)\),需要格外注意實現細節:
#include <cstdio>
#include <algorithm>
#include <set>
#include <cctype>
inline int read() {
int x = 0; char ch;
while (!isdigit(ch = getchar())) ;
while (x = x * 10 + (ch & 15), isdigit(ch = getchar())) ;
return x;
}
inline void write(int x) {
if (!x) putchar('0');
static char s[11]; int t = 0;
while (x) s[++t] = (x % 10) | 48, x /= 10;
while (t) putchar(s[t--]);
putchar('\n');
} // IO template
typedef long long LL;
const int Mod = 1000000007;
const int Inf = 0x3f3f3f3f;
const int MS = 200005; // 2 * MN
int N;
// No-Rotation Treap
inline unsigned ran() {
static unsigned x = 23333;
return x ^= x << 13, x ^= x >> 17, x ^= x << 5;
}
struct Mat {
#define F(i) for (int i = 0; i < 2; ++i)
int A[2][2];
Mat() {}
Mat(int t) { F(i) F(j) A[i][j] = i == j ? t : 0; }
inline friend Mat operator * (const Mat &p, const Mat &q) {
Mat r; LL A[2][2];
F(i) F(j) A[i][j] = 0;
F(i) F(j) F(k) A[i][k] += (LL)p.A[i][j] * q.A[j][k];
F(i) F(j) r.A[i][j] = A[i][j] % Mod;
return r;
} // Reduce [mod] Times
#undef F
} prd[MS], val[MS];
int Root, pri[MS], ls[MS], rs[MS], dif[MS], sdf[MS], cnt;
inline void NMdf(int id, int d) { // [id] Is A Leaf
dif[id] = sdf[id] = d;
val[id].A[0][0] = val[id].A[0][1] = 1;
val[id].A[1][0] = (d - 1) / 2, val[id].A[1][1] = d / 2;
prd[id] = val[id];
}
inline int NewNode(int d) { pri[++cnt] = ran(), NMdf(cnt, d); return cnt; }
inline int upd(int id) {
prd[id] = prd[rs[id]] * val[id] * prd[ls[id]];
sdf[id] = sdf[ls[id]] + dif[id] + sdf[rs[id]];
return id;
}
int Merge(int rt1, int rt2) {
if(!rt1) return rt2;
if(!rt2) return rt1;
if (pri[rt1] < pri[rt2]){
rs[rt1] = Merge(rs[rt1], rt2);
return upd(rt1);
}
else {
ls[rt2] = Merge(rt1, ls[rt2]);
return upd(rt2);
}
}
void Split(int rt, int k, int &rt1, int &rt2) {
if (!rt) { rt1 = rt2 = 0; return; }
if(k < sdf[ls[rt]] + dif[rt]) {
Split(ls[rt], k, rt1, rt2);
ls[rt] = rt2, rt2 = upd(rt);
}
else{
Split(rs[rt], k - sdf[ls[rt]] - dif[rt], rt1, rt2);
rs[rt] = rt1, rt1 = upd(rt);
}
}
inline int TMin(int rt) { while (ls[rt]) rt = ls[rt]; return rt; } // Minimum Existing Element, [rt] Exists
inline void TInsert(int pos) { // [pos] Does Not Exist
int rt1, rt2, rt3;
Split(Root, pos, rt1, rt2);
if (!rt2) { Root = Merge(rt1, NewNode(pos - (rt1 ? sdf[rt1] : 0))); return ; }
int lstp = rt1 ? sdf[rt1] : 0, nxtp = lstp + dif[TMin(rt2)];
Split(rt2, nxtp - lstp, rt2, rt3);
NMdf(rt2, nxtp - pos);
Root = Merge(Merge(rt1, NewNode(pos - lstp)), Merge(rt2, rt3));
}
inline void TDelete(int lb, int rb) { // [lb, rb, 2] Exists
int rt1, rt2, rt3, rt4;
Split(Root, rb, rt1, rt3);
Split(rt1, lb - 1, rt1, rt2);
if (!rt3) { Root = rt1; return ; }
int lstp = rt1 ? sdf[rt1] : 0, nxtp = rb + dif[TMin(rt3)];
Split(rt3, nxtp - rb, rt3, rt4);
NMdf(rt3, nxtp - lstp);
Root = Merge(rt1, Merge(rt3, rt4));
}
inline void TShiftString(int lb, int rb) { // [lb, rb, 2] Exists
int rt1, rt2, rt3, rt4, rt5;
Split(Root, rb, rt1, rt4);
Split(rt1, lb - 1, rt1, rt2);
int lstp = rt1 ? sdf[rt1] : 0;
Split(rt2, lb - lstp, rt2, rt3);
NMdf(rt2, lb - lstp + 1);
rt2 = Merge(rt2, rt3);
if (!rt4) { Root = Merge(rt1, rt2); return ; }
int nxtp = rb + dif[TMin(rt4)];
Split(rt4, nxtp - rb, rt4, rt5);
NMdf(rt4, nxtp - rb - 1);
Root = Merge(Merge(rt1, rt2), Merge(rt4, rt5));
}
struct C {
int min, max;
C(int l = 0, int r = 0) : min(l), max(r) {}
inline friend bool operator < (const C &p, const C &q) { return p.max < q.max; }
}; std::set<C> st;
typedef std::set<C>::iterator iter;
inline void Put(int pos) ; // Add Fib[pos]
inline void PutT1(iter it, int pos) {
TDelete(pos - 1, pos - 1);
int nmin = it->min, nmax = it->max;
st.erase(it);
if (nmin != nmax) st.insert(C(nmin, nmax - 2));
Put(pos + 1);
}
inline void PutT2(iter it, int pos) {
int nmax = it->max, nmin = it->min;
TDelete(pos + 1, nmax);
st.erase(it);
if (nmin <= pos - 1) st.insert(C(nmin, pos - 1));
Put(nmax + 1);
}
inline void PutT3(iter it, int pos) {
int nmax = it->max, nmin = it->min;
TDelete(pos, nmax);
st.erase(it);
if (nmin != pos) {
TShiftString(nmin, pos - 2);
st.insert(C(nmin + 1, pos - 1));
}
Put(nmax + 1);
if (nmin != 1) Put(nmin == 2 ? 1 : nmin - 2);
}
inline void Put(int pos) {
iter it = st.lower_bound(C(pos - 1, pos - 1));
if (it->min - 1 <= pos && pos <= it->max + 1) {
if (pos == it->max + 1) PutT1(it, pos);
else if ((it->max - pos) % 2 == 1) PutT2(it, pos);
else PutT3(it, pos);
}
else {
iter nxt = it, lst = it; --lst;
TInsert(pos);
int nmin = pos, nmax = pos;
if (nxt->min == pos + 2) nmax = nxt->max, st.erase(nxt);
if (lst->max == pos - 2) nmin = lst->min, st.erase(lst);
st.insert(C(nmin, nmax));
}
}
int main() {
prd[0] = val[0] = Mat(1);
st.insert(C(Inf, Inf)), st.insert(C(-Inf, -Inf));
N = read();
for (int i = 1; i <= N; ++i) {
Put(read());
write((prd[Root].A[0][0] + prd[Root].A[1][0]) % Mod);
}
return 0;
}
