Codeforces Round #FF (Div. 1) C (Segment_Tree)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/ShadowGhostH/article/details/81605761

题目

题意

给定一个长度为n的序列,对序列定义两种操作:
( 1 , l , r ) 在对应 [ l , r ] 的区间上,对 a i + F i l + 1 其中 i [ l , r ]   F i 为斐波那契数列的第 i 项。其中 F 1 = 1 F 2 = 1 F n = F n 1 + F n 2 ( n 2 )
( 2 , l , r ) [ l , r ] 区间元素求和,对 1 e 9 + 9 取模并输出。 i = l r a i ( m o d 1 e 9 + 9 )

分析

题意为一个较为经典的区间修改和区间查询问题,首先可以想到用线段树去维护,但是维护的难点在于,我们的区间修改并不是加上一个固定的值, 而是对应加上斐波那契数列的不同项。
我们考虑斐波那契数列的通项公式

F n = 1 5 [ ( 1 + 5 2 ) n ( 1 5 2 ) n ]

其中, 383008016 2 5 mod 1 e 9 + 9 ;
则, F n = 276601605 ( 690604013 n 308495997 n ) mod 1 e 9 + 9
上式可以通过二次剩余和逆元等一些方法求得,查了几个二次剩余的博客没有看懂,自己直接写了个暴力求了一下。Orz
而我们得到了模意义下的斐波那契数列通项公式之后,我们可以发现,这个数列被拆分成了,两个等比数列的差乘以一个常数的形式。而根据等比数列的求和公式 a 1 1 q 1 q n ,我们可知对于一个区间的加操作,我们可以通过维护两个不同的首项来直接获得区间加了多少(对应区间的长度是已知的)而求和公式很容易可以看出,满足结合律,所以我们的lazy也就可以直接维护区间对应首项即可,维护首相的时候可以直接对lazy标记进行加和操作。
原问题就转化为了维护两个等比数列的区间求和,区间修改的线段树。
E n d

代码

#define int long long
/**
F(n)%mod = 276601605*(691504013^n-308495997^n)%mod   mod=1e9+9
*/

const int q1 = 691504013;
const int q2 = 308495997;
const int mm = 276601605;

struct Node{
    int l, r;
    int sum1, sum2;          //特征值
    int lazy1, lazy2;        //分别维护两个等比数列
}tree[maxn<<2];              //开四倍空间
int num[maxn];               //数值数组
int qn[2][maxn];

void init(){
    ll now = 1;
    qn[0][0] = now;
    rep(i, 1, maxn){
        now = now*q1%mod;
        qn[0][i] = now;
    }
    qn[1][0] = now = 1;
    rep(i, 1, maxn){
        now = now*q2%mod;
        qn[1][i] = now;
    }
}

int qsum(int s, int flag, int n){
    ll ans = s;
    return mm*s%mod*(qn[flag][n]-1)%mod*qn[flag][1]%mod;
}

void build(int i, int l, int r){
    tree[i].l = l;
    tree[i].r = r;
    if(l == r){
        tree[i].sum1 = num[l];
        tree[i].sum2 = 0;
        tree[i].lazy1 = 0;
        tree[i].lazy2 = 0;
        return ;
    }

    int mid = (l+r)>>1;
    build(i<<1, l, mid);
    build(i<<1|1, mid+1, r);    //子树建树后更新特征值
    tree[i].sum1 = tree[i<<1].sum1 + tree[i<<1|1].sum1;
    tree[i].sum2 = tree[i<<1].sum2 + tree[i<<1|1].sum2;
    tree[i].lazy1 = 0;
    tree[i].lazy2 = 0;
    return ;
}

void push_down(int i){ //以lazy表示区间加为例更改特征值
    if(tree[i].lazy1){
        tree[i<<1].sum1 = (tree[i<<1].sum1 + qsum(tree[i].lazy1, 0, (tree[i<<1].r-tree[i<<1].l+1)))%mod;
        tree[i<<1].sum2 = (tree[i<<1].sum2 + qsum(tree[i].lazy2, 1, (tree[i<<1].r-tree[i<<1].l+1)))%mod;
        tree[i<<1|1].sum1 = (tree[i<<1|1].sum1 + qsum(tree[i].lazy1*qn[0][(tree[i<<1].r-tree[i<<1].l+1)]%mod, 0, (tree[i<<1|1].r-tree[i<<1|1].l+1)))%mod;
        tree[i<<1|1].sum2 = (tree[i<<1|1].sum2 + qsum(tree[i].lazy2*qn[1][(tree[i<<1].r-tree[i<<1].l+1)]%mod, 1, (tree[i<<1|1].r-tree[i<<1|1].l+1)))%mod;

        tree[i<<1].lazy1 = (tree[i<<1].lazy1+tree[i].lazy1)%mod;
        tree[i<<1].lazy2 = (tree[i<<1].lazy2+tree[i].lazy2)%mod;
        tree[i<<1|1].lazy1 = (tree[i<<1|1].lazy1+tree[i].lazy1*qn[0][(tree[i<<1].r-tree[i<<1].l+1)]%mod)%mod;
        tree[i<<1|1].lazy2 = (tree[i<<1|1].lazy2+tree[i].lazy2*qn[1][(tree[i<<1].r-tree[i<<1].l+1)]%mod)%mod;
        tree[i].lazy1 = tree[i].lazy2 = 0;
    }
}

void addlr(int i, int l, int r, int s){  // num[l~r] += k
    if(tree[i].l==l && tree[i].r==r){
        tree[i].sum1 = (tree[i].sum1 + qsum(qn[0][s], 0, (tree[i].r-tree[i].l+1)))%mod;
        tree[i].sum2 = (tree[i].sum2 + qsum(qn[1][s], 1, (tree[i].r-tree[i].l+1)))%mod;

        //cout<<"*"<<l<<" "<<r<<" "<<qsum(qn[0][s], 0, (tree[i].r-tree[i].l+1))-qsum(qn[1][s], 1, (tree[i].r-tree[i].l+1))<<endl;

        tree[i].lazy1 = (tree[i].lazy1 + qn[0][s])%mod;
        tree[i].lazy2 = (tree[i].lazy2 + qn[1][s])%mod;
        return ;
    }
    push_down(i);
    int mid = (tree[i].l+tree[i].r)>>1;
    if(mid >= r)
        addlr(i<<1, l, r, s);
    else if(mid < l)
        addlr(i<<1|1, l, r, s);
    else {
        addlr(i<<1, l, mid, s);
        addlr(i<<1|1, mid+1, r, s+(mid-l+1));
    }
    tree[i].sum1 = (tree[i<<1].sum1 + tree[i<<1|1].sum1)%mod;
    tree[i].sum2 = (tree[i<<1].sum2 + tree[i<<1|1].sum2)%mod;
}

int query(int i, int l, int r){  //以sum为例
    if(tree[i].l==l && tree[i].r==r)
        return (tree[i].sum1-tree[i].sum2+mod)%mod;

    push_down(i);

    int mid = (tree[i].l+tree[i].r)>>1;
    if(mid >= r)
        return query(i<<1, l, r);
    else if(mid < l)
        return query(i<<1|1, l, r);
    else
        return (query(i<<1, l, mid) + query(i<<1|1, mid+1, r))%mod;
}

signed main()
{
    init();

    int n, m;
    sldd(n, m);
    rep(i, 1, n+1)
        sld(num[i]);

    build(1, 1, n);

    rep(i, 0, m){
        int x, l, r;
        slddd(x, l, r);
        if(x==1){
            addlr(1, l, r, 1);
        }
        else if(x==2){
            pld(query(1, l, r));
        }
    }
}
/*
4 4
1 2 3 4
1 1 4
2 1 4
1 2 4
2 1 3
*/

总结

这个线段树分别维护了两个 s u m 值和两个 l a z y 标记,一开始写的时候写迷糊了,后来理清思路就好很多。
这次专门玩了一手define int long long 表示很开心,主函数改signed就可以了
代码因为开始思路不清晰所以有点丑
中间有多处取模可能会溢出或者忘记取模的部分,WA了两发,应多加注意

猜你喜欢

转载自blog.csdn.net/ShadowGhostH/article/details/81605761