• 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
  • 相关阅读:
    netbeans 快捷键
    Netbeans Platform的Lookup 边学边记
    Swing中的并发使用SwingWorker线程模式
    转:ExtJS:tabpanel 多个tab同时渲染问题
    NetBeans Platform Login Tutorial
    检测项目
    C#实现通过程序自动抓取远程Web网页信息
    WebClient类
    string字符串的方法
    WebClient的研究笔记认识WebClient
  • 原文地址:https://www.cnblogs.com/andyqsmart/p/4748286.html
Copyright © 2020-2023  润新知