Codeforces Round #FF (Div. 1) C. DZY Loves Fibonacci Numbers(线段树+斐波那契)

原题链接:C. DZY Loves Fibonacci Numbers


题目大意:


定义斐波那契数列: F 1 = 1 , F 2 = 1 ; F n = F n − 1 + F n − 2 ( n > 2 ) F_{1}=1,F_{2}=1;F_{n}=F_{n-1}+F_{n-2}(n>2) F1=1,F2=1;Fn=Fn1+Fn2(n>2)

现在给出一个长度为 n n n 正整数数组 a a a ,此外还有 m m m 次查询:

  • 1 1 1 l l l r r r 表示对区间 [ l , r ] [l,r] [l,r] 内的所有 a i a_{i} ai 加上 F i − l + 1 F_{i-l+1} Fil+1
  • 2 2 2 l l l r r r 表示对数组 a a a [ l , r ] [l,r] [l,r] 之内的所有元素求和 ( ∑ i = l r a i ) (\sum_{i=l}^{r}a_{i}) (i=lrai)

对于每个询问 2 2 2 ,输出答案对 1 0 9 + 7 10^{9}+7 109+7 取模后的结果 。

解题思路:


首先看起来就非常像线段树的题。

对操作二的处理非常方便,我们只需要子节点的区间值向上合并即可。

但是对操作一的下传处理很不好做。

对区间 [ l , r ] [l,r] [l,r] 内的所有 a i a_{i} ai 加上 F i − l + 1 F_{i-l+1} Fil+1 ,这玩意有 i i i l l l 的 影响,要是能把它们分别拆出来就好了。

首先注意到斐波那契有一个性质: F x + y = F x + 1 F y + F x F y − 1 F_{x+y}=F_{x+1}F_{y}+F_{x}F_{y-1} Fx+y=Fx+1Fy+FxFy1

那么对于这一题而言: F i − l + 1 = F i + 1 F − l + 1 + F i F − l F_{i-l+1}=F_{i+1}F_{-l+1}+F_{i}F_{-l} Fil+1=Fi+1Fl+1+FiFl

这样,我们就把 i i i − l + 1 -l+1 l+1 分开了。

那么原题的操作 1 1 1 就变成了:

F l − l + 1 + F ( l + 1 ) − l + 1 + . . . + F r − l + 1 = ( F l + 1 + F l + 2 + . . . + F r + 1 ) F − l + 1 + ( F l + F l + 1 + . . . + F r ) F − l F_{l-l+1}+F_{(l+1)-l+1}+...+F_{r-l+1}=(F_{l+1}+F_{l+2}+...+F_{r+1})F_{-l+1}+(F_{l}+F_{l+1}+...+F_{r})F_{-l} Fll+1+F(l+1)l+1+...+Frl+1=(Fl+1+Fl+2+...+Fr+1)Fl+1+(Fl+Fl+1+...+Fr)Fl

这样,操作 1 1 1 的标记下传和懒标记合并就可以直接做了。

但是斐波那契数列没有负数下标啊?

没关系,就像 F 3 = F 2 + F 1 F_{3}=F_{2}+F_{1} F3=F2+F1 一样,我们也可得 F 1 = F 3 − F 2 F_{1}=F_{3}-F_{2} F1=F3F2,这是满足的。

所以 F 0 = F 2 − F 1 , F − 1 = F 1 − F 0 , . . . , F − n = F − n + 2 − F − n + 1 F_{0}=F_{2}-F_{1},F_{-1}=F_{1}-F_{0},...,F_{-n}=F_{-n+2}-F_{-n+1} F0=F2F1,F1=F1F0,...,Fn=Fn+2Fn+1

我们将负数下标的斐波那契预处理出来,再处理原斐波那契的前缀和就好了。

具体而言:线段树维护区间和 s u m sum sum,再维护两个标记 a 1 , a 2 a1,a2 a1,a2

区间标记下推时:

s u m = s u m + ( p r e f i x F r + 1 − p r e f i x F l ) ∗ a 1 ′ + ( p r e f i x F r − p r e f i x F l − 1 ) ∗ a 2 ′ sum = sum + (prefixF_{r+1}-prefixF_{l})*a_1'+(prefixF_{r}-prefixF_{l-1})*a_2' sum=sum+(prefixFr+1prefixFl)a1+(prefixFrprefixFl1)a2
a 1 = a 1 + a 1 ′ a_{1}=a_{1}+a_{1}' a1=a1+a1
a 2 = a 2 + a 2 ′ a_{2}=a_{2}+a_{2}' a2=a2+a2

区间信息合并时:

s u m ′ = s u m l s o n + s u m r s o n sum'=sum_{lson}+sum_{rson} sum=sumlson+sumrson

其中 a 1 ′ , a 2 ′ a_{1}',a_{2}' a1,a2 为父亲节点下传的标记值,即所有未下传操作中 F − l + 1 , F − l F_{-l+1},F_{-l} Fl+1,Fl 的和, s u m ′ sum' sum 为父亲的区间和, s u m l s o n , s u m r s o n sum_{lson},sum_{rson} sumlson,sumrson 分别为左右儿子的区间和。(记得取模)

时间复杂度: O ( n log ⁡ n ) O(n \log n) O(nlogn)

AC代码:


#include <bits/stdc++.h>
using namespace std;

using PII = pair<int, int>;
using i64 = long long;

//线段树板子
template<class Info, class Tag>
struct Segtree {
#define lson k << 1, l, mid
#define rson k << 1 | 1, mid + 1, r
    int n;
    vector<Info> info;
    vector<Tag> tag;
    Segtree(int _n) : n(_n), info((_n + 5) << 2), tag((_n + 5) << 2) {};
    Segtree(vector<Info>& arr) : Segtree(arr.size() - 1) {
        function<void(int, int, int)> build = [&](int k, int l, int r) {
            if (l == r) {
                info[k] = arr[l];
                return;
            }
            int mid = l + r >> 1;
            build(lson), build(rson);
            pushup(k);
        };
        build(1, 1, n);
    }
    void pushdown(int k, int l, int r) {
        int mid = l + r >> 1, lt = k << 1, rt = k << 1 | 1;
        info[lt].down(tag[k], l, mid);
        info[rt].down(tag[k], mid + 1, r);
        tag[lt].down(tag[k]);
        tag[rt].down(tag[k]);
        tag[k] = Tag();//初始化tag
    }
    void pushup(int k) {
        info[k] = merge(info[k << 1], info[k << 1 | 1]);
    }
    void Modify(int k, int l, int r, int x, int y, const Tag& z) {
        if (l >= x && r <= y) {
            info[k].down(z, l, r);
            tag[k].down(z);
            return;
        }
        if (tag[k].change()) pushdown(k, l, r);
        int mid = l + r >> 1;
        if (x <= mid) Modify(lson, x, y, z);
        if (y > mid) Modify(rson, x, y, z);
        pushup(k);
    }
    Info Query(int k, int l, int r, int x, int y) {
        if (l >= x && r <= y) return info[k];
        if (tag[k].change()) pushdown(k, l, r);
        int mid = l + r >> 1;
        if (y <= mid) return Query(lson, x, y);
        if (x > mid) return Query(rson, x, y);
        return merge(Query(lson, x, y), Query(rson, x, y));
    }
    void Modify(int l, int r, const Tag& z) {
        Modify(1, 1, n, l, r, z);
    }
    Info Query(int l, int r) {
        return Query(1, 1, n, l, r);
    }
};

const int mod = 1e9 + 9, N = 3e5 + 10;

i64 fib[N], rfib[N], pfib[N];

//Tag内的 down 为标记之间的合并 t为父亲下传的标记
struct Tag {
    i64 a1{}, a2{};
    bool change() { return a1 || a2; }
    void down(const Tag& t) {
        a1 = (a1 + t.a1) % mod;
        a2 = (a2 + t.a2) % mod;
    }
};

//Info内的 down 为父亲标记对子节点的下传 t为父亲下传的标记
//Info内的 merge 为父亲节点对儿子节点信息的合并 a为左儿子 b为右儿子
struct Info {
    i64 sum;
    void down(const Tag& t, int l, int r) {
        sum = (sum + (pfib[r + 1] - pfib[l] + mod) % mod * t.a1 % mod) % mod;
        sum = (sum + (pfib[r] - pfib[l - 1] + mod) % mod * t.a2 % mod) % mod;
    }
    friend Info merge(const Info& a, const Info& b) {
        Info res;
        res.sum = (a.sum + b.sum) % mod;
        return res;
    }
};

void solve() {
    int n, m;
    cin >> n >> m;

    vector<Info> a(n + 1);

    for (int i = 1; i <= n; ++i) {
        cin >> a[i].sum;
    }

    Segtree<Info, Tag> Seg(a);

    int op, l, r;
    for (int i = 1; i <= m; ++i) {
        cin >> op >> l >> r;
        if (op == 1) {
            // a1 为 F[-l+1] / a2 为 F[-l]
            Seg.Modify(l, r, { rfib[l - 1], rfib[l] });
        } else {
            cout << Seg.Query(l, r).sum << '\n';
        }
    }
}

signed main() {

    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);

    fib[1] = fib[2] = 1;//斐波那契数列
    pfib[1] = 1, pfib[2] = 2;//前缀和斐波那契数列
    rfib[0] = 0, rfib[1] = 1, rfib[2] = mod - 1;//斐波那契数列负下标
    for (int i = 3; i < N; ++i) {
        fib[i] = (fib[i - 1] + fib[i - 2]) % mod;
        pfib[i] = (pfib[i - 1] + fib[i]) % mod;
        rfib[i] = (rfib[i - 2] - rfib[i - 1] + mod) % mod;
    }

    int t = 1; //cin >> t;
    while (t--) solve();

    return 0;
}

  • 24
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

柠檬味的橙汁

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值