CF 446C 线段树

CF446C题意:
给你一个数列\(a_i\),有两种操作:区间求和;\(\sum_{i=l}^{r}(a[i]+=fib[i-l+1])\)。\(fib\)是斐波那契数列。
思路
(一)
\(fib[n] = \frac{\sqrt5}{5}\times [(\frac{1+\sqrt5}{2})^n-(\frac{1-\sqrt5}{2})^n]\)

有关取模、同余、逆元的一些东西:
\(p = 1e9 + 9\)
\(383008016^2 ≡ 5 (mod\;p)\)
\(383008016 ≡ \sqrt5 (mod\;p)\)
\(\frac{1}{\sqrt5}≡276601605(mod\;p)\)
\(383008016的逆元 = 276601605\)
\((1+\sqrt5)/2≡691504013(mod\;p)\)
\(383008017\times 2的逆元 = 691504013\)
\((1-\sqrt5)/2≡308495997(mod\;p)\)
\((p-383008016+1)\times 2的逆元 = 308495997\)

\(fib[n] = 276601605\times [(691504013)^n-(308495997)^n] (mod\;\;p)\)
等比数列求和:\(sum = \frac{a}{a-1} \times (a^n - 1) (mod\;\;p) = a^2(a^n-1)(mod\;\;p)=a^{n+2}-a^2(mod\;\;p)\)
当\(p=1e9+9, a = 691504013或308495997时成立\)。
所以本题我们只需要用线段树lazy标记维护两个等比数列第一项为一次项的系数即可。代码如下。

#include<bits/stdc++.h>
#define lson rt<<1
#define rson rt<<1|1
using namespace std;
typedef long long LL;

const int MXN = 5e5 + 6;
const int INF = 0x3f3f3f3f;
const LL mod = 1000000009;
const LL p1 = 691504013;
const LL p2 = 308495997;
const LL p3 = 276601605;

int n, m;
LL ar[MXN], pre[MXN], mul1[MXN], mul2[MXN];
LL sum[MXN<<2], lazy1[MXN<<2], lazy2[MXN<<2];
LL ksm(LL a, LL b) {
    LL res = 1;
    for(;b;b>>=1,a=a*a%mod) {
        if(b&1) res = res * a %mod;
    }
    return res;
}
void check(LL &a) {
    if(a >= mod) a %= mod;
}
void push_up(int rt) {
    sum[rt] = sum[lson] + sum[rson]; check(sum[rt]);
}
void push_down(int l,int r,int rt) {
    if(lazy1[rt] == 0 && lazy2[rt] == 0) return;
    LL a = lazy1[rt], b = lazy2[rt];
    int mid = (l + r) >> 1;
    int len1 = mid-l+1, len2 = r - mid;
    lazy1[lson] += a; lazy2[lson] += b;
    sum[lson] = sum[lson] + a*((mul1[len1+2]-mul1[2])%mod+mod); check(sum[lson]);
    sum[lson] = (sum[lson] - b*((mul2[len1+2]-mul2[2])%mod+mod))%mod + mod; check(sum[lson]);
    lazy1[rson] += a*mul1[len1]%mod; lazy2[rson] += b*mul2[len1]%mod;
    sum[rson] = sum[rson] + a*mul1[len1]%mod*((mul1[len2+2]-mul1[2])%mod+mod); check(sum[rson]);
    sum[rson] = (sum[rson] - b*mul2[len1]%mod*((mul2[len2+2]-mul2[2])%mod+mod))%mod + mod; check(sum[rson]);
    lazy1[rt] = lazy2[rt] = 0;
    check(lazy1[lson]);check(lazy1[rson]);check(lazy2[lson]);check(lazy2[rson]);
}
void update(int L,int R,int l,int r,int rt,LL x,LL y) {
    if(L <= l && r <= R) {
        lazy1[rt] += x; lazy2[rt] += y;
        check(lazy1[rt]); check(lazy2[rt]);
        sum[rt] = sum[rt] + x*((mul1[r-l+3]-mul1[2])%mod+mod); check(sum[rt]);
        sum[rt] = (sum[rt] - y*((mul2[r-l+3]-mul2[2])%mod+mod))%mod + mod; check(sum[rt]);
        return;
    }
    push_down(l, r, rt);
    int mid = (l + r) >> 1;
    if(L > mid) update(L,R,mid+1,r,rson,x,y);
    else if(R <= mid) update(L,R,l,mid,lson,x,y);
    else {
        update(L,mid,l,mid,lson,x,y);
        update(mid+1,R,mid+1,r,rson,mul1[mid-L+1]*x%mod,mul2[mid-L+1]*y%mod);
    }
    push_up(rt);
}
LL query(int L,int R,int l,int r,int rt) {
    if(L <= l && r <= R) {
        return sum[rt];
    }
    push_down(l,r,rt);
    int mid = (l+r) >> 1;
    if(L > mid) return query(L,R,mid+1,r,rson);
    else if(R <= mid) return query(L,R,l,mid,lson);
    else {
        LL ans = query(L,mid,l,mid,lson);
        ans += query(mid+1,R,mid+1,r,rson);
        check(ans);
        return ans;
    }
}
int main() {
    //printf("%d\n", ksm(691504013-1,mod-2));
    //printf("%d\n", ksm(308495997-1,mod-2));
    //F(n) = √5/5[((1+√5)/2)^n-((1-√5)/2)^n]
    //383008016^2 ≡ 5 (mod 1e9 + 9)
    //383008016 ≡ sqrt(5) (mod 1e9 + 9)
    //printf("%lld\n", ksm(383008016,mod-2));//1/sqrt(5)≡276601605(mod)
    //printf("%lld\n", 383008017*ksm(2,mod-2)%mod);//(1+sqrt(5))/2≡691504013(mod)
    //printf("%lld\n", (mod-383008016+1)*ksm(2,mod-2)%mod);//(1-sqrt(5))/2≡308495997(mod)
    scanf("%d%d", &n, &m);
    mul1[0] = mul2[0] = 1;
    for(int i = 1; i < 301000; ++i) {
        mul1[i] = mul1[i-1] * p1;
        mul2[i] = mul2[i-1] * p2;
        check(mul1[i]); check(mul2[i]);
    }
    for(int i = 1; i <= n; ++i) scanf("%lld", &ar[i]), pre[i] = (pre[i-1] + ar[i])%mod;
    while(m --) {
        int opt, l, r;
        scanf("%d%d%d", &opt, &l, &r);
        if(opt == 1) {
            update(l, r, 1, n, 1, 1, 1);
        }else {
            printf("%lld\n", ((p3*query(l,r,1,n,1)%mod+pre[r]-pre[l-1])%mod+mod)%mod);
        }
    }
    return 0;
}

(二)
斐波纳契数列的一些性质:
性质1:对于一个满足斐波那契性质的数列,如果我们已知它的前两项,我们可以O(1)的得到它的任意一项和任意前缀和!
性质2:两个满足斐波那契性质的数列相加后,依然是斐波那契数列。前两项的值分别为两个的和。

所以本题我们用线段树的\(lazy\)标记维护给这个区间各项加上的\(fib\)数列的前两项的值。通过这个\(lazy\)标记我们可以\(O(1)\)更新区间和,因为斐波纳契数列满足可加性,所以我们\(lazy\)标记也可以很轻松的\(push\_down\)操作。代码如下。

#include<bits/stdc++.h>
#define lson rt<<1
#define rson rt<<1|1
using namespace std;
typedef long long LL;

const int MXN = 5e5 + 6;
const int INF = 0x3f3f3f3f;
const LL mod = 1000000009;
const LL p1 = 691504013;
const LL p2 = 308495997;
const LL p3 = 276601605;

int n, m;
LL ar[MXN], fib[MXN];
LL sum[MXN<<2], lazy1[MXN<<2], lazy2[MXN<<2];
LL ksm(LL a, LL b) {
    LL res = 1;
    for(;b;b>>=1,a=a*a%mod) {
        if(b&1) res = res * a %mod;
    }
    return res;
}
LL hn(int n,LL a,LL b) {
    if(n == 1) return (a%mod+mod)%mod;
    if(n == 2) return (b%mod+mod)%mod;
    return ((a*fib[n-2] + b*fib[n-1])%mod+mod)%mod;
}
void check(LL &a) {
    if(a >= mod) a %= mod;
}
void push_up(int rt) {
    sum[rt] = sum[lson] + sum[rson]; check(sum[rt]);
}
void build(int l,int r,int rt) {
    if(l == r) {
        sum[rt] = ar[l];
        return;
    }
    int mid = (l + r) >> 1;
    build(l, mid, lson); build(mid+1, r, rson);
    push_up(rt);
}
void push_down(int l,int r,int rt) {
    if(lazy1[rt] == 0 && lazy2[rt] == 0) return;
    LL a = lazy1[rt], b = lazy2[rt];
    int mid = (l + r) >> 1;
    int len1 = mid-l+1, len2 = r - mid;
    lazy1[lson] += a; lazy2[lson] += b;
    sum[lson] = (sum[lson] + hn(len1+2,a,b) - b)%mod+mod;
    lazy1[rson] += hn(len1+1,a,b); lazy2[rson] += hn(len1+2,a,b);
    sum[rson] = (sum[rson] + hn(len2+2,hn(len1+1,a,b),hn(len1+2,a,b))-hn(len1+2,a,b))%mod+mod;
    check(sum[lson]); check(sum[rson]);
    check(lazy1[lson]);check(lazy1[rson]);check(lazy2[lson]);check(lazy2[rson]);
    lazy1[rt] = lazy2[rt] = 0;
}
void update(int L,int R,int l,int r,int rt,LL x, LL y) {
    if(L <= l && r <= R) {
        lazy1[rt] += x; lazy2[rt] += y;
        check(lazy1[rt]); check(lazy2[rt]);
        sum[rt] = (sum[rt] + hn(r-l+1+2,x,y) - y)%mod+mod; check(sum[rt]);
        return;
    }
    push_down(l, r, rt);
    int mid = (l + r) >> 1;
    if(L > mid) update(L,R,mid+1,r,rson,x,y);
    else if(R <= mid) update(L,R,l,mid,lson,x,y);
    else {
        update(L,mid,l,mid,lson,x,y);
        update(mid+1,R,mid+1,r,rson,hn(mid-L+1+1,x,y), hn(mid-L+1+2,x,y));
    }
    push_up(rt);
}
LL query(int L,int R,int l,int r,int rt) {
    if(L <= l && r <= R) {
        return sum[rt];
    }
    push_down(l,r,rt);
    int mid = (l+r) >> 1;
    if(L > mid) return query(L,R,mid+1,r,rson);
    else if(R <= mid) return query(L,R,l,mid,lson);
    else {
        LL ans = query(L,mid,l,mid,lson);
        ans += query(mid+1,R,mid+1,r,rson);
        check(ans);
        return ans;
    }
}
int main() {
    scanf("%d%d", &n, &m);
    fib[1] = fib[2] = 1;
    for(int i = 3; i < 301000; ++i) {
        fib[i] = fib[i-1] + fib[i-2];
        check(fib[i]);
    }
    for(int i = 1; i <= n; ++i) scanf("%lld", &ar[i]);
    build(1, n, 1);
    while(m --) {
        int opt, l, r;
        scanf("%d%d%d", &opt, &l, &r);
        if(opt == 1) {
            update(l, r, 1, n, 1, 1, 1);
        }else {
            printf("%lld\n", query(l,r,1,n,1));
        }
    }
    return 0;
}

原文地址:https://www.cnblogs.com/Cwolf9/p/10276206.html

时间: 2024-11-10 10:56:07

CF 446C 线段树的相关文章

Codeforces 446C 线段树 递推Fibonacci公式

聪哥推荐的题目 区间修改和区间查询,但是此题新颖之处就在于他的区间修改不是个定值,而是从L 到 R 分别加 F1.F2....Fr-l+1 (F为斐波那契数列) 想了一下之后,觉得用fib的前缀和来解决,每次做懒惰标记记录下当前区间是从哪个L开始加起的,敲了一半之后发现有问题,就跟上次遇到的懒惰标记问题一样,这是个覆盖性的懒惰标记,每次向下传递后,都要先清除孩子的,清除孩子的也有可能要清除son's son,所以要一直pushdown下去,否则就会错,但这样就会超时. 能不能有个累加型的标记让我

CF 338E, 线段树

这道题真是太伤节奏了,做完之后好几天没动弹了. 题目大意:给你两个数组a.b,a长b短,对于以a每个位置开头与b等长的连续子序列,我们询问是否有这样一个匹配方式让子序列每个数和b中每个数一一对应相加且和大于给定的常数h. 解:没有想明白,去看官解没弄懂出题人是怎么分块瞎搞的.搜了一下题解,发现有一个更巧妙的解法,首先我们对b排序不会影响到结果,然后对于某个数,他在排序后的b里的合法匹配方案一定是连续的一段,这样就转化成了线段问题.对于Bn(从小到大排序的B数组),我们可知必须子序列里有n个数和他

CF 633G 线段树+bitset

大意是一棵树两种操作,第一种是某一节点子树所有值+v,第二种问子树中节点模m出现了多少种m以内的质数. 第一种操作非常熟悉了,把每个节点dfs过程中的pre和post做出来,对序列做线段树.维护取模也不是问题.第二种操作,可以利用bitset记录质数出现情况.所以整个线段树需要维护bitset的信息. 对于某一个bitset x,如果子树所有值需要加y,则x=(x<<y)|(x>>(m-y)) 一开始写挂了几次,有一点没注意到,因为我bitset直接全都是1000,而不是m,所以上

CF 498D 线段树

大意是有n段路,每一段路有个值a,通过每一端路需要1s,如果通过这一段路时刻t为a的倍数,则需要等待1s再走,也就是需要2s通过. 比较头疼的就是相邻两个数之间会因为数字不同制约,一开始想a的范围是2-6,处理这几个数字互相之间的关系,还是想岔了. 正解应当是开60个线段树,因为2-6的LCM是60,也就是所有数字模2-6,结果的循环节长度为60.所以如果从i到j,开始时刻如果为0,则答案一定与开始时刻为60相同. 第x个线段树某个节点范围如果是i和j,维护的便是开始时刻模60为x的情况下,从i

CF19D 线段树+STL各种应用

http://codeforces.com/problemset/problem/19/D Description Pete and Bob invented a new interesting game. Bob takes a sheet of paper and locates a Cartesian coordinate system on it as follows: point (0,?0) is located in the bottom-left corner, Ox axis

【线段树】【树状数组】【CF 121E】幸运数列

1922. [CF 121E]幸运数列 ★★★ 输入文件:cf121e.in 输出文件:cf121e.out 简单对比 时间限制:3 s 内存限制:256 MB [题目描述] 对于欧洲人来说,"幸运数"是指那些十进制只由4或7组成的数.财务员Petya需要维护一个支持如下操作的整数数列: add l r d - 表示将[l, r]区间内的所有数加上一个正整数d(). count l r - 统计[l, r]区间内有多少个"幸运数".() 请你帮助Petya实现它.

CF#52 C Circular RMQ (线段树区间更新)

Description You are given circular array a0,?a1,?...,?an?-?1. There are two types of operations with it: inc(lf,?rg,?v) - this operation increases each element on the segment [lf,?rg] (inclusively) by v; rmq(lf,?rg) - this operation returns minimal v

codeforces 446C DZY Loves Fibonacci Numbers(数学 or 数论+线段树)

In mathematical terms, the sequence Fn of Fibonacci numbers is defined by the recurrence relation F1 = 1; F2 = 1; Fn = Fn - 1 + Fn - 2 (n > 2). DZY loves Fibonacci numbers very much. Today DZY gives you an array consisting of n integers: a1, a2, ...,

codeforces 446C DZY Loves Fibonacci Numbers 数论+线段树成段更新

DZY Loves Fibonacci Numbers Time Limit:4000MS     Memory Limit:262144KB     64bit IO Format:%I64d & %I64u Submit Status Appoint description:  System Crawler  (2014-07-14) Description In mathematical terms, the sequence Fn of Fibonacci numbers is defi