zoukankan      html  css  js  c++  java
  • ACM学习历程—Codeforces 446C DZY Loves Fibonacci Numbers(线段树 && 数论)

    Description

    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, ..., an. Moreover, there are m queries, each query has one of the two types:

    1. Format of the query "1 lr". In reply to the query, you need to add Fi - l + 1 to each element ai, where l ≤ i ≤ r. 
    2. Format of the query "2 lr". In reply to the query you should output the value of modulo 1000000009 (109 + 9). 

    Help DZY reply to all the queries.

    Input

    The first line of the input contains two integers n and m (1 ≤ n, m ≤ 300000). The second line contains n integers a1, a2, ..., an (1 ≤ ai ≤ 109) — initial array a.

    Then, m lines follow. A single line describes a single query in the format given in the statement. It is guaranteed that for each query inequality 1 ≤ l ≤ r ≤ n holds.

    Output

    For each query of the second type, print the value of the sum on a single line.

    Sample Input

    Input

    4 4
    1 2 3 4
    1 1 4
    2 1 4
    1 2 4
    2 1 3

    Output

    17
    12

    Sample Output

    Test case #1Total explored area: 180.00 

    Hint

    After the first query, a = [2, 3, 5, 7].

    For the second query, sum = 2 + 3 + 5 + 7 = 17.

    After the third query, a = [2, 4, 6, 9].

    For the fourth query, sum = 2 + 4 + 6 = 12.

    这个题目是段更新的线段树,但是题目段内增加的不是固定值,是Fibonacci数列,而且每个点也不是增加的固定的某个Fibonacci值,增加的是Fi - l + 1。

    目前有两种处理方式。

    方式一:(等效替代)

    对于Fn = Fn - 1 + Fn - 2,根据特征方程可以得到解是

    Fn = (1/sqrt(5)*(((1+sqrt(5))/2)^n-((1-sqrt(5))/2)^n)

    但是式子里面带有sqrt(5),double型精度丢失严重,于是这里需要一个小技巧。

    对于sqrt(5),由于式子展开后不包含sqrt(5),而且sqrt(5)唯一的特征就是平方后等于5,这样就可以找一个替代的数,在模10^9+9的情况下,平方等于5就可以。这样这个数在模10^9+9的情况下,运算结果是和sqrt(5)一致的。

    然后弄个程序找一下是383008016和616991993,随便一个就行。

    然后就是1/sqrt(5)这个也需要在模运算下处理,这个式子的含义就是sqrt(5)的逆元,这样就弄个程序找一下逆元就可以了。

    然后式子就转换成Fn = K*(X^n-Y^n)了。

    就是两个等比数列了。

    然后对于和s = sumFrom(1,n)(K*X^i)

    可以拆分成 s = sumFrom(1,n)(K*X^i) = sumFrom(1, t)(K*X^i) + sumFrom(t+1, n)(K*X^i)

    = sumFrom(1, t)(K*X^i) + X^t*sumFrom(1, n-t)(K*X^i)

    这样区间段更新的时候,对每一个等比数列就满足区间的拆分了。

    这样就只需要维护前面的系数k了:

    刚进去时,X的系数是K,Y的系数是-K

    第一次区间拆分后前一段区间是K,后一段就是X^t*K了。

    这个t就是由前一段更新区间长度得到的。(这里需要注意的是,我代码里面用x和y数组已经预先处理的两个等比数列的前n项和,但是程序里在区间拆分时会出现下标访问-1的情况,这里需要特判一下。)

    最后区间段更新就是求一个等比数列的前n项和,这里我预先进行了处理。

    代码:

    #include <iostream>
    #include <cstdio>
    #include <cstdlib>
    #include <cstring>
    #include <cmath>
    #include <set>
    #include <map>
    #include <queue>
    #include <string>
    #include <algorithm>
    #define LL long long
    
    using namespace std;
    
    //fn = (1/sqrt(5)*(((1+sqrt(5))/2)^n-((1-sqrt(5))/2)^n)
    const LL MOD = 1e9+9;
    const LL UNIT = 383008016;//sqrt(5)在模MOD的情况下的等价数,616991993也是
    const LL X = ((1+UNIT+MOD)/2)%MOD;//特征根,加MOD为了化为偶数
    const LL Y = (1-UNIT+MOD)/2;//特征根
    const LL K = 276601605;//1/sqrt(5)
    
    //线段树
    //区间每点增值,求区间和
    const int maxN = 300005;
    LL x[maxN], y[maxN];
    int n, m, a[maxN];
    struct node
    {
        int lt, rt;
        LL val, mul1, mul2;//mul是区间乘的值,val是区间和
    }tree[4*maxN];
    
    //向下更新
    void pushDown(int id)
    {
        LL tmp;
        if (tree[id].mul1 != 0)
        {
            tree[id<<1].mul1 += tree[id].mul1;
            tree[id<<1].mul1 %= MOD;
            tree[id<<1].val += x[tree[id<<1].rt-tree[id<<1].lt+1]*tree[id].mul1%MOD;
            tree[id<<1].val %= MOD;
    
            tmp = x[tree[id<<1].rt-tree[id<<1].lt+1]-x[tree[id<<1].rt-tree[id<<1].lt];
            tmp = (tmp%MOD+MOD)%MOD;
            tmp = tree[id].mul1*tmp%MOD;
            tree[id<<1|1].mul1 += tmp;
            tree[id<<1|1].mul1 %= MOD;
            tree[id<<1|1].val += x[tree[id<<1|1].rt-tree[id<<1|1].lt+1]*tmp%MOD;
            tree[id<<1|1].val %= MOD;
            tree[id].mul1 = 0;
        }
        if (tree[id].mul2 != 0)
        {
            tree[id<<1].mul2 += tree[id].mul2;
            tree[id<<1].mul2 %= MOD;
            tree[id<<1].val += y[tree[id<<1].rt-tree[id<<1].lt+1]*tree[id].mul2%MOD;
            tree[id<<1].val %= MOD;
    
            tmp = y[tree[id<<1].rt-tree[id<<1].lt+1]-y[tree[id<<1].rt-tree[id<<1].lt];
            tmp = (tmp%MOD+MOD)%MOD;
            tmp = tree[id].mul2*tmp%MOD;
            tree[id<<1|1].mul2 += tmp;
            tree[id<<1|1].mul2 %= MOD;
            tree[id<<1|1].val += y[tree[id<<1|1].rt-tree[id<<1|1].lt+1]*tmp%MOD;
            tree[id<<1|1].val %= MOD;
            tree[id].mul2 = 0;
        }
    }
    
    //向上更新
    void pushUp(int id)
    {
        tree[id].val = tree[id<<1].val + tree[id<<1|1].val;
        tree[id].val %= MOD;
    }
    
    //建立线段树
    void build(int lt, int rt, int id)
    {
        tree[id].lt = lt;
        tree[id].rt = rt;
        tree[id].val = 0;//每段的初值,根据题目要求
        tree[id].mul1 = tree[id].mul2 = 0;
        if (lt == rt)
        {
            tree[id].val = a[lt];
            return;
        }
        int mid = (lt + rt) >> 1;
        build(lt, mid, id<<1);
        build(mid + 1, rt, id<<1|1);
        pushUp(id);
    }
    
    void add(int lt, int rt, int id, LL pls1, LL pls2)
    {
        pls1 = (pls1%MOD+MOD)%MOD;
        pls2 = (pls2%MOD+MOD)%MOD;
        if (lt <= tree[id].lt && rt >= tree[id].rt)
        {
            tree[id].mul1 += pls1;
            tree[id].mul1 %= MOD;
            tree[id].mul2 += pls2;
            tree[id].mul2 %= MOD;
            tree[id].val += pls1*x[tree[id].rt-tree[id].lt+1]%MOD;
            tree[id].val += pls2*y[tree[id].rt-tree[id].lt+1]%MOD;
            tree[id].val %= MOD;
            return;
        }
        pushDown(id);
        int mid = (tree[id].lt + tree[id].rt) >> 1;
        if (lt <= mid)
            add(lt, rt, id<<1, pls1, pls2);
        if (rt > mid)
            if (mid >= max(tree[id<<1].lt,lt))
                add(lt, rt, id<<1|1,
                    pls1*(x[mid-max(tree[id<<1].lt,lt)+1]-x[mid-max(tree[id<<1].lt,lt)]),
                    pls2*(y[mid-max(tree[id<<1].lt,lt)+1]-y[mid-max(tree[id<<1].lt,lt)]));
            else
                add(lt, rt, id<<1|1, pls1, pls2);
        pushUp(id);
    }
    
    //查询某段区间内的和
    int query(int lt, int rt, int id)
    {
        if (lt <= tree[id].lt && rt >= tree[id].rt)
            return tree[id].val;
        pushDown(id);
        int mid = (tree[id].lt + tree[id].rt) >> 1;
        int ans = 0;
        if (lt <= mid)
            ans += query(lt, rt, id<<1);
        if (rt > mid)
            ans += query(lt, rt, id<<1|1);
        return ans%MOD;
    }
    
    void init()
    {
        x[0] = y[0] = 0;
        x[1] = X;
        y[1] = Y;
        for (int i = 2; i < maxN; ++i)
        {
            x[i] = (X*x[i-1]+X)%MOD;
            y[i] = (Y*y[i-1]+Y)%MOD;
        }
    }
    
    void input()
    {
        for (int i = 1; i <= n; ++i)
            scanf("%d", &a[i]);
        build(1, n, 1);
    }
    
    void work()
    {
        int op, from, to;
        LL ans;
        for (int i = 0; i < m; ++i)
        {
            scanf("%d%d%d", &op, &from, &to);
            if (op == 1)
                add(from, to, 1, K, -K);
            else
            {
                ans = query(from, to, 1);
                printf("%I64d
    ", ans);
            }
        }
    }
    
    int main()
    {
        //freopen("test.in", "r", stdin);
        init();
        while (scanf("%d%d", &n, &m) != EOF)
        {
            input();
            work();
        }
        return 0;
    }
    View Code

    方式二:(Fibonacci性质)

    假设某两项a, b,那么后面的项必然是a+b, a+2b, 2a+3b......

    a的系数序列为k[i] = 1, 0, 1, 1, 2.....从第0项开始,

    b的系数从第一项开始。

    对于f[i], f[i+1]后面某一项,f[p]

    那么f[p] = k[p-i]f[i] + k[p-i+1]f[i+1]

    此外,

    sumFrom(lt, rt)f[p]

    = sumFrom(lt, rt)(k[p-lt]f[lt]+k[p-lt+1]f[lt+1])

    = f[lt]sumFrom(lr, rt)k[p-lt] + f[lt+1]sumFrom(lt, rt)k[p-lt+1]

    = f[lt]s[rt-lt] + f[lt+1](s[rt-lt+1]-s[0])

    此外还有一个前提是两个Fibonacci数列的和依旧是Fibonacci。

    然后维护系数f[lt]f[lt+1]就可以了。

    不过依旧需要注意的是当更新区间完全在右子树时需要特殊判断u1u2是直接传入右子树还是需要位移后传入。

    代码:

      1 #include <iostream>
      2 #include <cstdio>
      3 #include <cstdlib>
      4 #include <cstring>
      5 #include <cmath>
      6 #include <set>
      7 #include <map>
      8 #include <queue>
      9 #include <string>
     10 #include <algorithm>
     11 #define LL long long
     12 
     13 using namespace std;
     14 
     15 //线段树
     16 //区间每点增值,求区间和
     17 const LL MOD = 1e9+9;
     18 const int maxN = 300005;
     19 int a[maxN], n, m;
     20 LL k[maxN], s[maxN];
     21 struct node
     22 {
     23     int lt, rt;
     24     LL val, u1, u2;//u1,u2表示以这两个起始的Fibonacci数列
     25 }tree[4*maxN];
     26 
     27 //向下更新
     28 void pushDown(int id)
     29 {
     30     LL tmp, u1, u2;
     31     if (tree[id].u1 != 0 || tree[id].u2 != 0)
     32     {
     33         tree[id<<1].u1 += tree[id].u1;
     34         tree[id<<1].u2 += tree[id].u2;
     35         tree[id<<1].u1 %= MOD;
     36         tree[id<<1].u2 %= MOD;
     37         tmp = tree[id].u1*s[tree[id<<1].rt-tree[id<<1].lt]%MOD;
     38         tmp += tree[id].u2*(s[tree[id<<1].rt-tree[id<<1].lt+1]-s[0])%MOD;
     39         tmp %= MOD;
     40         tree[id<<1].val += tmp;
     41         tree[id<<1].val %= MOD;
     42 
     43         u1 = k[tree[id<<1|1].lt-tree[id<<1].lt]*tree[id].u1%MOD;
     44         u1 += k[tree[id<<1|1].lt-tree[id<<1].lt+1]*tree[id].u2%MOD;
     45         u1 %= MOD;
     46         u2 = k[tree[id<<1|1].lt-tree[id<<1].lt+1]*tree[id].u1%MOD;
     47         u2 += k[tree[id<<1|1].lt-tree[id<<1].lt+2]*tree[id].u2%MOD;
     48         u2 %= MOD;
     49         tree[id<<1|1].u1 += u1;
     50         tree[id<<1|1].u2 += u2;
     51         tree[id<<1|1].u1 %= MOD;
     52         tree[id<<1|1].u2 %= MOD;
     53         tmp = u1*s[tree[id<<1|1].rt-tree[id<<1|1].lt]%MOD;
     54         tmp += u2*(s[tree[id<<1|1].rt-tree[id<<1|1].lt+1]-s[0])%MOD;
     55         tmp %= MOD;
     56         tree[id<<1|1].val += tmp;
     57         tree[id<<1|1].val %= MOD;
     58         tree[id].u1 = 0;
     59         tree[id].u2 = 0;
     60     }
     61 }
     62 
     63 //向上更新
     64 void pushUp(int id)
     65 {
     66     tree[id].val = (tree[id<<1].val + tree[id<<1|1].val)%MOD;
     67 }
     68 
     69 //建立线段树
     70 void build(int lt, int rt, int id)
     71 {
     72     tree[id].lt = lt;
     73     tree[id].rt = rt;
     74     tree[id].val = 0;//每段的初值,根据题目要求
     75     tree[id].u1 = tree[id].u2 = 0;
     76     if (lt == rt)
     77     {
     78         tree[id].val = a[lt];
     79         return;
     80     }
     81     int mid = (lt + rt) >> 1;
     82     build(lt, mid, id<<1);
     83     build(mid+1, rt, id<<1|1);
     84     pushUp(id);
     85 }
     86 
     87 void add(int lt, int rt, int id, int u1, int u2)
     88 {
     89     u1 %= MOD;
     90     u2 %= MOD;
     91     if (lt <= tree[id].lt && rt >= tree[id].rt)
     92     {
     93         LL tmp;
     94         tree[id].u1 += u1;
     95         tree[id].u2 += u2;
     96         tmp = u1*s[tree[id].rt-tree[id].lt]%MOD;
     97         tmp += u2*(s[tree[id].rt-tree[id].lt+1]-s[0])%MOD;
     98         tmp %= MOD;
     99         tree[id].val += tmp;
    100         tree[id].val %= MOD;
    101         return;
    102     }
    103     pushDown(id);
    104     int mid = (tree[id].lt + tree[id].rt) >> 1;
    105     if (lt <= mid)
    106         add(lt, rt, id<<1, u1, u2);
    107     if (rt > mid)
    108         if (mid-max(lt, tree[id].lt) >= 0)
    109             add(lt, rt, id<<1|1,
    110                 k[mid+1-max(lt, tree[id].lt)]*u1%MOD+k[mid+2-max(lt, tree[id].lt)]*u2%MOD,
    111                 k[mid+2-max(lt, tree[id].lt)]*u1%MOD+k[mid+3-max(lt, tree[id].lt)]*u2%MOD);
    112         else
    113             add(lt, rt, id<<1|1, u1, u2);
    114     pushUp(id);
    115 }
    116 
    117 //查询某段区间内的和
    118 int query(int lt, int rt, int id)
    119 {
    120     if (lt <= tree[id].lt && rt >= tree[id].rt)
    121         return tree[id].val;
    122     pushDown(id);
    123     int mid = (tree[id].lt + tree[id].rt) >> 1;
    124     int ans = 0;
    125     if (lt <= mid)
    126         ans += query(lt, rt, id<<1);
    127     if (rt > mid)
    128         ans += query(lt, rt, id<<1|1);
    129     return ans%MOD;
    130 }
    131 
    132 void init()
    133 {
    134     k[0] = 1;
    135     k[1] = 0;
    136     s[0] = s[1] = 1;
    137     for (int i = 2; i < maxN; ++i)
    138     {
    139         k[i] = (k[i-1]+k[i-2])%MOD;
    140         s[i] = (s[i-1]+k[i])%MOD;
    141     }
    142 }
    143 
    144 void input()
    145 {
    146     for (int i = 1; i <= n; ++i)
    147         scanf("%d", &a[i]);
    148     build(1, n, 1);
    149 }
    150 
    151 void work()
    152 {
    153     int op, from, to;
    154     LL ans;
    155     for (int i = 0; i < m; ++i)
    156     {
    157         scanf("%d%d%d", &op, &from, &to);
    158         if (op == 1)
    159             add(from, to, 1, 1, 1);
    160         else
    161         {
    162             ans = query(from, to, 1);
    163             printf("%I64d
    ", ans);
    164         }
    165     }
    166 }
    167 
    168 int main()
    169 {
    170     //freopen("test.in", "r", stdin);
    171     init();
    172     while (scanf("%d%d", &n, &m) != EOF)
    173     {
    174         input();
    175         work();
    176     }
    177     return 0;
    178 }
    View Code
  • 相关阅读:
    不可或缺 Windows Native (15)
    不可或缺 Windows Native (14)
    不可或缺 Windows Native (13)
    不可或缺 Windows Native (12)
    不可或缺 Windows Native (11)
    不可或缺 Windows Native (10)
    不可或缺 Windows Native (9)
    不可或缺 Windows Native (8)
    不可或缺 Windows Native (7)
    不可或缺 Windows Native (6)
  • 原文地址:https://www.cnblogs.com/andyqsmart/p/4748286.html
Copyright © 2011-2022 走看看