题解 CF718C Sasha and Array
写在前面
这道题太野蛮了!
Solution
需要维护两个操作,区间加和区间求和。
考虑线段树。
但是斐波那契数怎么搞?
\(O(n)\) 预处理?显然会爆掉。
我们知道矩阵快速幂可以在 \(\log x\) 的时间内求出一个斐波那契数。
考虑一个及其野蛮的操作,把线段树内维护的点换成矩阵。
我们知道,对于第 \(n - 1\) 项,有:
\[\begin{bmatrix}
f_{n - 1} & f_{n - 2} \\
\end{bmatrix} \times \begin{bmatrix}
1 & 1 \\
1 & 0
\end{bmatrix} = \begin{bmatrix}
f_{n} & f_{n - 1} \\
\end{bmatrix}\]
设 \(base = \begin{bmatrix} 1 & 1 \\ 1 & 0 \end{bmatrix}\)
假设我们有矩阵 \(z = \begin{bmatrix} f_{a_i} & f_{a_i-1} \end {bmatrix}\) ,如果对其加上 \(x\) ,我们可以直接进行这么一步操作:
\[\begin{bmatrix} f_{a_i+x} & f_{a_i-1+x} \end {bmatrix} = z \times base^x
\]
注意这里的乘法指的是矩阵乘法。
怎么区间合并?直接相加就行。
我们可以手模一下,发现它满足分配律,即:
\[\begin{bmatrix} f_a & f_{a-1} \end{bmatrix} \times base + \begin{bmatrix} f_b & f_{b-1} \end{bmatrix} \times base = \begin{bmatrix} f_a+f_b & f_{a-1} + f_{b-1} \end{bmatrix} \times base
\]
所以就可以直接进行合并了!
只要我们把矩阵当成点来看,那么区间修改查询等操作就和普通的线段树没有什么区别了。
预处理复杂度 \(O(\sum\limits_{i=1}^n \log a_i)\),单次修改和查询都是 \(O(\log x)\),可能会带一个 \(8\) 的常数。所以复杂度是正确的,可以通过。
题解区好像有用通项公式直接算的,通过预处理某些东西后跑起来也是飞快。
Tips
- lazy 要初始化为单位矩阵,push_down 时相关判断也应进行修改。
- 注意取模。
- \(a_i <= 2\) 时要单独构造,其他情况使用快速幂即可。
- 修改的时候可以直接把 \(base^x\) 这个矩阵传进去,减少计算量。
实现细节看代码吧,自以为自己的马蜂很好看
Code
/*
Work by: Suzt_ilymics
Problem: 不知名屑题
Knowledge: 垃圾算法
Time: O(能过)
*/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#define int long long
#define LL long long
#define orz cout<<"lkp AK IOI!"<<endl
using namespace std;
const int MAXN = 1e5+5;
const int INF = 1e9+7;
const int mod = 1e9+7;
struct Matrix {
int a[3][3];
Matrix () { memset(a, false, sizeof a); }
void Init() { a[1][1] = a[2][2] = 1, a[1][2] = a[2][1] = 0; }
bool pd() { return a[1][1] == 1 && a[2][2] == 1 && a[1][2] == 0 && a[2][1] == 0; }
Matrix operator + (const Matrix b) {
Matrix res;
for(int i = 1; i <= 2; ++i)
for(int j = 1; j <= 2; ++j)
res.a[i][j] = (a[i][j] + b.a[i][j]) % mod;
return res;
}
Matrix operator * (const Matrix b) {
Matrix res;
for(int k = 1; k <= 2; ++k)
for(int i = 1; i <= 2; ++i)
for(int j = 1; j <= 2; ++j)
res.a[i][j] = (res.a[i][j] + a[i][k] * b.a[k][j]) % mod;
return res;
}
Matrix operator ^ (int b) {
Matrix res, Base;
for(int i = 1; i <= 2; ++i) res.a[i][i] = 1;
for(int i = 1; i <= 2; ++i) for(int j = 1; j <= 2; ++j) Base.a[i][j] = a[i][j];
while(b) {
if(b & 1) res = res * Base;
Base = Base * Base;
b >>= 1;
}
return res;
}
}base, ans;
int n, m, a[MAXN];
int read(){
int s = 0, f = 0;
char ch = getchar();
while(!isdigit(ch)) f |= (ch == '-'), ch = getchar();
while(isdigit(ch)) s = (s << 1) + (s << 3) + ch - '0' , ch = getchar();
return f ? -s : s;
}
namespace Seg {
#define lson i << 1
#define rson i << 1 | 1
struct Tree {
Matrix sum, lazy;
}tree[MAXN << 2];
void Push_up(int i) { tree[i].sum = tree[lson].sum + tree[rson].sum; }
void Build(int i, int l, int r) {
tree[i].lazy.Init();
if(l == r) {
if(a[l] == 1) tree[i].sum.a[1][1] = 1;
else if(a[l] == 2) tree[i].sum.a[1][1] = tree[i].sum.a[1][2] = 1;
else tree[i].sum = ans * (base ^ (a[l] - 2));
return ;
}
int mid = (l + r) >> 1;
Build(lson, l, mid), Build(rson, mid + 1, r);
Push_up(i);
}
void Push_down(int i) {
if(tree[i].lazy.pd()) return ;
tree[lson].lazy = tree[lson].lazy * tree[i].lazy;
tree[rson].lazy = tree[rson].lazy * tree[i].lazy;
tree[lson].sum = tree[lson].sum * tree[i].lazy;
tree[rson].sum = tree[rson].sum * tree[i].lazy;
tree[i].lazy.Init();
}
void Modify(int i, int l, int r, int L, int R, Matrix k) {
if(L <= l && r <= R) {
tree[i].lazy = tree[i].lazy * k, tree[i].sum = tree[i].sum * k;
return ;
}
Push_down(i);
int mid = (l + r) >> 1;
if(mid >= L) Modify(lson, l, mid, L, R, k);
if(mid < R) Modify(rson, mid + 1, r, L, R, k);
Push_up(i);
}
int Query(int i, int l, int r, int L, int R) {
if(L <= l && r <= R) return tree[i].sum.a[1][1];
Push_down(i);
int mid = (l + r) >> 1, ans = 0;
if(mid >= L) ans = (ans + Query(lson, l, mid, L, R)) % mod;
if(mid < R) ans = (ans + Query(rson, mid + 1, r, L, R)) % mod;
return ans;
}
}
signed main()
{
base.a[1][1] = base.a[1][2] = base.a[2][1] = 1;
ans.a[1][1] = ans.a[1][2] = 1;
n = read(), m = read();
for(int i = 1; i <= n; ++i) a[i] = read();
Seg::Build(1, 1, n);
for(int i = 1, opt, l, r, k; i <= m; ++i) {
opt = read(), l = read(), r = read();
if(opt == 1) {
k = read();
Seg::Modify(1, 1, n, l, r, base^k);
} else {
printf("%lld\n", Seg::Query(1, 1, n, l, r));
}
}
return 0;
}