1. 程式人生 > 實用技巧 >「CF446C」 DZY Loves Fibonacci Numbers

「CF446C」 DZY Loves Fibonacci Numbers

「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(n\sqrt 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]))<<'\n';
		}
		if(tot==lim) rebuild();
	}
	return 0;
}