题目
题意
给定一个长度为n的序列,对序列定义两种操作:
(
1
,
 
l
,
 
r
)
(1,\, l,\, r)
(1,l,r) 在对应
[
l
,
 
r
]
[l,\, r]
[l,r] 的区间上,对
a
i
+
F
i
−
l
+
1
a_i + F_{i-l+1}
ai+Fi−l+1其中
i
∈
[
l
,
 
r
]
F
i
i\in[l,\, r]\ F_i
i∈[l,r] Fi为斐波那契数列的第
i
i
i项。其中
F
1
=
1
F
2
=
1
F
n
=
F
n
−
1
+
F
n
−
2
(
n
≥
2
)
F_1 = 1\quad F_2=1 \quad F_n = F_{n-1} + F_{n-2} (n\ge2)
F1=1F2=1Fn=Fn−1+Fn−2(n≥2)
(
2
,
 
l
,
 
r
)
(2, \, l,\,r)
(2,l,r) 对
[
l
,
 
r
]
[l,\,r]
[l,r]区间元素求和,对
1
e
9
+
9
1e9+9
1e9+9取模并输出。
∑
i
=
l
r
a
i
(
m
o
d
  
1
e
9
+
9
)
\sum_{i=l}^ra_i(mod\; 1e9+9)
∑i=lrai(mod1e9+9)
分析
题意为一个较为经典的区间修改和区间查询问题,首先可以想到用线段树去维护,但是维护的难点在于,我们的区间修改并不是加上一个固定的值, 而是对应加上斐波那契数列的不同项。
我们考虑斐波那契数列的通项公式
F
n
=
1
5
[
(
1
+
5
2
)
n
−
(
1
−
5
2
)
n
]
F_n = \frac{1}{\sqrt{5}}[(\frac{1+\sqrt{5}}{2})^n -(\frac{1-\sqrt{5}}{2})^n ]
Fn=51[(21+5)n−(21−5)n]
其中,
38300801
6
2
≡
5
m
o
d
  
1
e
9
+
9
;
383008016^2\equiv5\mod{1e9+9};
3830080162≡5mod1e9+9;
则,
F
n
=
276601605
(
69060401
3
n
−
30849599
7
n
)
m
o
d
  
1
e
9
+
9
F_n = 276601605\left(690604013^n-308495997^n\right)\mod 1e9+9
Fn=276601605(690604013n−308495997n)mod1e9+9
上式可以通过二次剩余和逆元等一些方法求得,查了几个二次剩余的博客没有看懂,自己直接写了个暴力求了一下。Orz
而我们得到了模意义下的斐波那契数列通项公式之后,我们可以发现,这个数列被拆分成了,两个等比数列的差乘以一个常数的形式。而根据等比数列的求和公式
a
1
1
−
q
n
1
−
q
a_1\frac{1-q^n}{1-q}
a11−q1−qn,我们可知对于一个区间的加操作,我们可以通过维护两个不同的首项来直接获得区间加了多少(对应区间的长度是已知的)而求和公式很容易可以看出,满足结合律,所以我们的lazy也就可以直接维护区间对应首项即可,维护首相的时候可以直接对lazy标记进行加和操作。
原问题就转化为了维护两个等比数列的区间求和,区间修改的线段树。
E
n
d
End
End
代码
#define int long long
/**
F(n)%mod = 276601605*(691504013^n-308495997^n)%mod mod=1e9+9
*/
const int q1 = 691504013;
const int q2 = 308495997;
const int mm = 276601605;
struct Node{
int l, r;
int sum1, sum2; //特征值
int lazy1, lazy2; //分别维护两个等比数列
}tree[maxn<<2]; //开四倍空间
int num[maxn]; //数值数组
int qn[2][maxn];
void init(){
ll now = 1;
qn[0][0] = now;
rep(i, 1, maxn){
now = now*q1%mod;
qn[0][i] = now;
}
qn[1][0] = now = 1;
rep(i, 1, maxn){
now = now*q2%mod;
qn[1][i] = now;
}
}
int qsum(int s, int flag, int n){
ll ans = s;
return mm*s%mod*(qn[flag][n]-1)%mod*qn[flag][1]%mod;
}
void build(int i, int l, int r){
tree[i].l = l;
tree[i].r = r;
if(l == r){
tree[i].sum1 = num[l];
tree[i].sum2 = 0;
tree[i].lazy1 = 0;
tree[i].lazy2 = 0;
return ;
}
int mid = (l+r)>>1;
build(i<<1, l, mid);
build(i<<1|1, mid+1, r); //子树建树后更新特征值
tree[i].sum1 = tree[i<<1].sum1 + tree[i<<1|1].sum1;
tree[i].sum2 = tree[i<<1].sum2 + tree[i<<1|1].sum2;
tree[i].lazy1 = 0;
tree[i].lazy2 = 0;
return ;
}
void push_down(int i){ //以lazy表示区间加为例更改特征值
if(tree[i].lazy1){
tree[i<<1].sum1 = (tree[i<<1].sum1 + qsum(tree[i].lazy1, 0, (tree[i<<1].r-tree[i<<1].l+1)))%mod;
tree[i<<1].sum2 = (tree[i<<1].sum2 + qsum(tree[i].lazy2, 1, (tree[i<<1].r-tree[i<<1].l+1)))%mod;
tree[i<<1|1].sum1 = (tree[i<<1|1].sum1 + qsum(tree[i].lazy1*qn[0][(tree[i<<1].r-tree[i<<1].l+1)]%mod, 0, (tree[i<<1|1].r-tree[i<<1|1].l+1)))%mod;
tree[i<<1|1].sum2 = (tree[i<<1|1].sum2 + qsum(tree[i].lazy2*qn[1][(tree[i<<1].r-tree[i<<1].l+1)]%mod, 1, (tree[i<<1|1].r-tree[i<<1|1].l+1)))%mod;
tree[i<<1].lazy1 = (tree[i<<1].lazy1+tree[i].lazy1)%mod;
tree[i<<1].lazy2 = (tree[i<<1].lazy2+tree[i].lazy2)%mod;
tree[i<<1|1].lazy1 = (tree[i<<1|1].lazy1+tree[i].lazy1*qn[0][(tree[i<<1].r-tree[i<<1].l+1)]%mod)%mod;
tree[i<<1|1].lazy2 = (tree[i<<1|1].lazy2+tree[i].lazy2*qn[1][(tree[i<<1].r-tree[i<<1].l+1)]%mod)%mod;
tree[i].lazy1 = tree[i].lazy2 = 0;
}
}
void addlr(int i, int l, int r, int s){ // num[l~r] += k
if(tree[i].l==l && tree[i].r==r){
tree[i].sum1 = (tree[i].sum1 + qsum(qn[0][s], 0, (tree[i].r-tree[i].l+1)))%mod;
tree[i].sum2 = (tree[i].sum2 + qsum(qn[1][s], 1, (tree[i].r-tree[i].l+1)))%mod;
//cout<<"*"<<l<<" "<<r<<" "<<qsum(qn[0][s], 0, (tree[i].r-tree[i].l+1))-qsum(qn[1][s], 1, (tree[i].r-tree[i].l+1))<<endl;
tree[i].lazy1 = (tree[i].lazy1 + qn[0][s])%mod;
tree[i].lazy2 = (tree[i].lazy2 + qn[1][s])%mod;
return ;
}
push_down(i);
int mid = (tree[i].l+tree[i].r)>>1;
if(mid >= r)
addlr(i<<1, l, r, s);
else if(mid < l)
addlr(i<<1|1, l, r, s);
else {
addlr(i<<1, l, mid, s);
addlr(i<<1|1, mid+1, r, s+(mid-l+1));
}
tree[i].sum1 = (tree[i<<1].sum1 + tree[i<<1|1].sum1)%mod;
tree[i].sum2 = (tree[i<<1].sum2 + tree[i<<1|1].sum2)%mod;
}
int query(int i, int l, int r){ //以sum为例
if(tree[i].l==l && tree[i].r==r)
return (tree[i].sum1-tree[i].sum2+mod)%mod;
push_down(i);
int mid = (tree[i].l+tree[i].r)>>1;
if(mid >= r)
return query(i<<1, l, r);
else if(mid < l)
return query(i<<1|1, l, r);
else
return (query(i<<1, l, mid) + query(i<<1|1, mid+1, r))%mod;
}
signed main()
{
init();
int n, m;
sldd(n, m);
rep(i, 1, n+1)
sld(num[i]);
build(1, 1, n);
rep(i, 0, m){
int x, l, r;
slddd(x, l, r);
if(x==1){
addlr(1, l, r, 1);
}
else if(x==2){
pld(query(1, l, r));
}
}
}
/*
4 4
1 2 3 4
1 1 4
2 1 4
1 2 4
2 1 3
*/
总结
这个线段树分别维护了两个
s
u
m
sum
sum值和两个
l
a
z
y
lazy
lazy标记,一开始写的时候写迷糊了,后来理清思路就好很多。
这次专门玩了一手define int long long 表示很开心,主函数改signed就可以了
代码因为开始思路不清晰所以有点丑
中间有多处取模可能会溢出或者忘记取模的部分,WA了两发,应多加注意