「CF446C」 DZY Loves Fibonacci Numbers
这里提供一种优美的根号分治做法。
首先,我们考虑一种不太一样的暴力。对于一个区间加斐波那契数的操作 ([a,b]),以及一个区间求和的操作 ([p,q]),仅需预处理斐波那契数列前缀和,我们就可以在 (O(1)) 的时间内算出 ([a,b]) 对 ([p,q]) 的贡献。
这样的复杂度为 (O(n^2))。
再考虑传统暴力:对于一个区间加斐波那契数的操作 ([a,b]),直接将其作用到原数列 (a) 上。
那么我们可不可以将多个区间加斐波那契数的操作一起作用到原数列上呢?
答案是肯定的。
首先有一个显然的结论:若 (a_i=a_{i-1}+a_{i-2},b_j=b_{j-1}+b_{j-2}),则有 (a_i+b_j=a_{i-1}+a_{i-2}+b_{j-1}+b_{j-2})。
这启发我们可以对于多次区间加同时进行递推。
我们维护每个区间加操作的起止点,在操作开始时加入数 (1) 进行递推,操作结束时删除该操作所用到的两个数,就可以在 (O(n)) 的时间内将多次操作同时进行。
具体可见代码,非常清晰。
若我们每 (T) 次操作后将当前的多个区间加斐波那契数的操作一起作用到原数列上,其余时候暴力,这样的最坏复杂度为 (O(frac{n^2}{T}+Tn))。取 $T=sqrt n $ 时复杂度最优,为 (O(nsqrt n))。虽在理论复杂度上逊于线段树解法,但其常数小,代码复杂度低,实际表现与实现一般的线段树相差无几甚至略优,不失为一种良好的解题方式。
同时注意,块长的调整可能会使该算法的效率进一步提升,因为显然 (O(Tn)) 部分是达不到上界的,故代码对于重构的实现方式与题解略有不同。
贴代码:
#include<bits/stdc++.h>
using namespace std;
const int p=1e9+9;
const int maxn=3e5+5;
int fib[maxn],sum[maxn];
int b[maxn],v[maxn],val[maxn];
int l[maxn],r[maxn],tot;
int n,m;
int md(int x){
if(x>p) return x-p;
if(x<0) return x+p;
return x;
}
vector<int> p1[maxn],p2[maxn];
void rebuild(){
for(int i=1;i<=tot;++i){
p1[l[i]].emplace_back(i);
p2[r[i]].emplace_back(i);
}
int a=0,b=0;
for(int i=1;i<=n;++i){
int c=md(a+b);
val[i]=md(val[i]+c);
a=b,b=c;
for(auto x:p1[i]) b=md(b+1),val[i]=md(val[i]+1);
for(auto x:p2[i]){
int L=l[x],R=r[x];
b=md(b-fib[R-L+1]);
a=md(a-fib[R-L]);
}
v[i]=md(v[i-1]+val[i]);
}
for(int i=1;i<=tot;++i){
p1[l[i]].clear();
p2[r[i]].clear();
}
tot=0;
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>m;
fib[1]=1,fib[2]=1;
for(int i=3;i<=n;++i) fib[i]=md(fib[i-1]+fib[i-2]);
for(int i=1;i<=n;++i) sum[i]=md(sum[i-1]+fib[i]);
for(int i=1;i<=n;++i) cin>>val[i],v[i]=md(v[i-1]+val[i]);
int lim=sqrt(m);
for(int _=1;_<=m;++_){
int opt,a,b;cin>>opt>>a>>b;
if(opt==1) l[++tot]=a,r[tot]=b;
else{
int ans=0;
for(int i=1;i<=tot;++i){
int L=max(a,l[i]),R=min(b,r[i]);
if(L<=R){
ans=md(ans+md(sum[R-l[i]+1]-sum[L-l[i]]));
if(ans>p) ans-=p;
}
}
cout<<md(ans+md(v[b]-v[a-1]))<<'
';
}
if(tot==lim) rebuild();
}
return 0;
}