【题解】CF316E3 Summer Homework

题意

原题传送门

给定一个长度为 n n n的序列 A ( n ≤ 200000 ) A (n \leq 200000) A(n200000),要求支持单点修改和区间加,并在线询问函数 S ( l , r ) m o d    1 0 9 S(l,r)\mod 10^9 S(l,r)mod109的值,其中函数 S ( l , r ) S(l,r) S(l,r)的定义如下:
S ( l , r ) = ∑ i = 0 r − l A i + l f i f 为 斐 波 那 契 数 列 , f 0 = f 1 = 1 , f i = f i − 1 + f i − 2 S(l,r)=\sum_{i=0}^{r-l}A_{i+l}f_i \\ f为斐波那契数列,f_0=f_1=1,f_i=f_{i-1}+f_{i-2} S(l,r)=i=0rlAi+lfiff0=f1=1,fi=fi1+fi2

分析

首先一眼数据结构题,两眼线段树题。

涉及到序列上的斐波那契数列操作,又要动态计算,第一想法是用线段树来维护矩阵,然后用矩阵乘法快速算出斐波那契数列的值。这样做当然是可行的(而且好像也是这道题最主流的做法),但是码量稍大,并且需要封装一个矩阵结构体。对于我这种想偷懒的矩阵菜鸡很不友好。

那么有没有什么避开矩阵的方法呢?当然是有的,不过在正事开始之前需要先介绍一个十分重要的引理:
f i = f k f i − k + f k − 1 f i − k − 1 ( 1 ≤ k < i ) f_i=f_kf_{i-k}+f_{k-1}f_{i-k-1} \qquad (1 \leq k < i) fi=fkfik+fk1fik1(1k<i)
实际上感性理解一下即可,也可以用类似数学归纳法的方法来证明:
显 然 当 i ≤ 1 时 结 论 成 立 那 么 当 i > 1 时 , 我 们 假 设 f i − 1 和 f i − 2 都 满 足 性 质 那 么 我 们 选 取 一 个 k ( 1 ≤ k < i − 2 , 因 为 当 k ≥ i − 2 时 结 论 显 然 成 立 ) f i = f i − 1 + f i − 2 = f k f i − k − 1 + f k − 1 f i − k − 2 + f k f i − k − 2 + f k − 1 f i − k − 3 = f k ( f i − k − 1 + f i − k − 2 ) + f k ( f i − k − 2 + f i − k − 3 ) = f k f i − k + f k − 1 f i − k − 1 ∴ 结 论 成 立 显然当i\leq1时结论成立 \\ 那么当i>1时,我们假设f_{i-1}和f_{i-2}都满足性质 \\ 那么我们选取一个k \quad (1\leq k < i-2,因为当k\geq i-2时结论显然成立)\\ \begin{aligned} f_i&=f_{i-1}+f_{i-2} \\ &=f_kf_{i-k-1}+f_{k-1}f_{i-k-2}+f_kf_{i-k-2}+f_{k-1}f_{i-k-3}\\ &=f_k(f_{i-k-1}+f_{i-k-2})+f_k(f_{i-k-2}+f_{i-k-3}) \\ &=f_kf_{i-k}+f_{k-1}f_{i-k-1}\\ \therefore 结论成立 \end{aligned} i1i>1fi1fi2k(1k<i2ki2)fi=fi1+fi2=fkfik1+fk1fik2+fkfik2+fk1fik3=fk(fik1+fik2)+fk(fik2+fik3)=fkfik+fk1fik1
这个引理有什么作用呢?我们可以举个栗子来感受一下:

假设我们操作前的 S 1 S_1 S1 A 1 f 0 + A 2 f 1 + A 3 f 2 A_1f_0+A_2f_1+A_3f_2 A1f0+A2f1+A3f2,并且我们知道 S 2 = A 1 f 1 + A 2 f 2 + A 3 f 3 S_2=A_1f_1+A_2f_2+A_3f_3 S2=A1f1+A2f2+A3f3的值,我们现在想要把这个序列的斐波那契系数平移 4 4 4个单位,也就是说把最终的 S S S变为 A 1 f 4 + A 2 f 5 + A 3 f 6 A_1f_4+A_2f_5+A_3f_6 A1f4+A2f5+A3f6,那么我们可以进行如下操作:先把 S 1 S_1 S1乘上 f 2 f_2 f2,再把 S 2 S_2 S2乘上 f 3 f_3 f3,然后把它们加起来,就可以得到最终的 S S S。具体用柿子表示就是:
S 1 f 2 + S 2 f 3 = A 1 f 0 f 2 + A 2 f 1 f 2 + A 3 f 2 f 2 + A 1 f 1 f 3 + A 2 f 2 f 3 + A 3 f 3 f 3 = A 1 ( f 0 f 2 + f 1 f 3 ) + A 2 ( f 1 f 2 + f 2 f 3 ) + A 3 ( f 2 f 2 + f 3 f 3 ) = A 1 f 4 + A 2 f 5 + A 3 f 6 ( 运 用 引 理 ) = S \begin{aligned} S_1f_2+S_2f_3&=A_1f_0f_2+A_2f_1f_2+A_3f_2f_2+A_1f_1f_3+A_2f_2f_3+A_3f_3f_3\\ &=A_1(f_0f_2+f_1f_3)+A_2(f_1f_2+f_2f_3)+A_3(f_2f_2+f_3f_3) \\ &=A_1f_4+A_2f_5+A_3f_6 \qquad (运用引理)\\ &=S \end{aligned} S1f2+S2f3=A1f0f2+A2f1f2+A3f2f2+A1f1f3+A2f2f3+A3f3f3=A1(f0f2+f1f3)+A2(f1f2+f2f3)+A3(f2f2+f3f3)=A1f4+A2f5+A3f6()=S

再抽象一点来讲,如果我们要将 S 1 S_1 S1的系数平移 k k k个单位,最终得到的 S S S即为 S 1 f k − 2 + S 2 f k − 1 S_1f_{k-2}+S_2f_{k-1} S1fk2+S2fk1

于是我们就可以在线段树上的每个节点维护相应区间的 S 1 S_1 S1 S 2 S_2 S2值,在向上合并的时候把右儿子平移一段距离再加上左儿子即可(查询时同理);区间修改的时候打上标记,向下 p u s h _ d o w n push\_down push_down的时候,我们发现整段区间实际上是加上了 t a g tag tag倍的一段斐波那契数列的前缀和。于是我们预处理斐波那契数列的前缀和即可。

代码

模数写成1e9+7调了半个小时

#include 
#define ll long long
#define P ((ll)1e9)
#define MAX 200005
#define lc(x) (x<<1)
#define rc(x) (x<<1|1)
#define mid ((l+r)>>1)
using namespace std;

int n, m;
ll f[MAX], sum[MAX];

void init(){
    f[0] = f[1] = 1;
    sum[0] = 1, sum[1] = 2;
    for(int i = 2; i < MAX; ++i){
        f[i] = (f[i-1]+f[i-2])%P;
        sum[i] = (sum[i-1]+f[i])%P;
    }
}

ll s1[MAX*4], s2[MAX*4], tag[MAX*4];

ll calc(ll p, ll st){       //p节点代表的区间,第一项系数从f_st开始的和
    if(st == 0) return s1[p];
    else if(st == 1) return s2[p];
    else return (s1[p]*f[st-2]%P+s2[p]*f[st-1]%P) % P;
}

void push_up(int p, int l, int r){
    s1[p] = (s1[lc(p)]+calc(rc(p), mid-l+1)) % P;
    s2[p] = (s2[lc(p)]+calc(rc(p), mid-l+2)) % P;
}

void add(int p, int l, int r, ll k){
    (tag[p] += k) %= P;
    (s1[p] += sum[r-l]*k%P) %= P;
    (s2[p] += (sum[r-l+1]+P-1)%P*k%P) %= P;
}

void push_down(int p, int l, int r){
    if(!tag[p]) return;
    add(lc(p), l, mid, tag[p]);
    add(rc(p), mid+1, r, tag[p]);
    tag[p] = 0;
}

void update(int p, int l, int r, int ul, int ur, ll k){
    if(l >= ul && r <= ur){
        add(p, l, r, k);
        return;
    }
    push_down(p, l, r);
    if(mid >= ul) update(lc(p), l, mid, ul, ur, k);
    if(mid < ur) update(rc(p), mid+1, r, ul, ur, k);
    push_up(p, l, r);
}

void modify(int p, int l, int r, int u, ll k){
    if(l == r){
        s1[p] = s2[p] = k;
        return;
    }
    push_down(p, l, r);
    if(mid >= u) modify(lc(p), l, mid, u, k);
    else modify(rc(p), mid+1, r, u, k);
    push_up(p, l, r);
}

ll query(int p, int l, int r, int ul, int ur){
    if(l >= ul && r <= ur){
        return calc(p, l-ul);
    }
    push_down(p, l, r);
    ll res = 0;
    if(mid >= ul) (res += query(lc(p), l, mid, ul, ur)) %= P;
    if(mid < ur) (res += query(rc(p), mid+1, r, ul, ur)) %= P;
    return res;
}

int main()
{
    init();
    cin >> n >> m;
    int t, x, y, k;
    for(int i = 1; i <= n; ++i){
        scanf("%d", &x);
        modify(1, 1, n, i, x);
    }
    for(int i = 1; i <= m; ++i){
        scanf("%d%d%d", &t, &x, &y);
        if(t == 1){
            modify(1, 1, n, x, y);
        }
        else if(t == 2){
            printf("%lld\n", query(1, 1, n, x, y));
        }
        else{
            scanf("%d", &k);
            update(1, 1, n, x, y, k);
        }
    }

    return 0;
}

你可能感兴趣的:(【题解】CF316E3 Summer Homework)