zoukankan      html  css  js  c++  java
  • 神奇的差分法(内附树状数组的一点扩展)

    差分法是我们所用的一个强力的武器!

    有这把武器你就可以统治世界。。。

    一个大佬曾经讲过,一但碰到区间修改的题,就要优先考虑差分。

    普通差分法

    我们有时做题,会发现这么一种题。

    给你长度为n的序列,m次操作,有两种:1. 让[l,r]区间加上k。2. 查询一个点的值。
    

    典型的区间修改,单点查询。
    单点查询简单。
    但是区间修改怎么做?
    暴力卡常。。。
    线段树。。。
    比较正经的做法是差分,差分是什么?

    在这里插入图片描述

    对于一个序列a,定义另一个序列b,(b[i]=a[i]-a[i-1]),则叫b数组为a数组的差分数组,这样有什么优势呢?

    首先,如果要求a[i],我们会发现就是(b[i]+a[i-1]),不断拆开,(a[i]=b[i]+b[i-1]+b[i-2]+b[i-3]+....+b[1]),是不是很棒棒,就是前缀和!

    但是单点查询O(n)呀

    先讲这样怎么修改,假设是([l,r])区间加上k,那么我们就让(b[l])加上(k),让(b[r+1])减去(k)就行了。这样,在([l,r])区间内,每个数都会加上(b[l])多出的(k),但是在(r)之后,我们也会因为(b[r+1])减去了(k)从而和(b[l])(k)抵消。

    这样,区间修改就完成了!

    但是单点查询又复杂了,它可以表达成前缀和,前缀和......树状数组维护就可以了!
    是不是很不错!

    这里给大家一点扩展!

    至于树状数组区间查询,区间修改,我粘上一个大佬的话,我认为很不错!


    我们还是需要引入delta数组,这里的delta[i]表示区间a[i...j]都需要加上的值的和。那么当我们需要将区间[l,r]上的每个数都加上x时,我们还是可以直接在树状数组上将delta[l]加上x,delta[r+1]减去x。

    那么问题来了,如何查询区间[l,r]的和?

    我们设a[1...i]的和为sum[i],根据delta数组的定义,则:

    [sum[i]=sum_{j=1}^ia[j]+sum_{j=1}^idelta[j]*(i-j+1) ]

    [sum[i]=sum_{j=1}^ia[j]+(i+1)*sum_{j=1}^idelta[j]-sum_{j=1}^idelta[j]*j ]

    这样我们就不难看sum[i]是由哪三个部分组成的了。我们需要用一个asum数组维护a数组的前缀和,delta1与delta2两个树状数组,delta1维护delta数组的和,delta2维护delta[i]*i的和,代码如下:

    void add(int *arr int pos,int x){
        while(pos<=n) arr[pos]+=x,pos+=lowbit(pos);
    }
    void modify(int l,int r,int x){
        add(d1,l,x),add(d1,r+1,-x),add(d2,l,x*l),add(d2,r+1,-x*(r+1));
    }
    int getsum(int *arr,int pos){
        int sum=0;
        while(pos) sum+=arr[pos],pos-=lowbit(pos);
        return sum;
    }
    int query(int l,int r){
        return asum[r]+r*getsum(d1,r)-getsum(d2,r)-(asum[l-1]+l*getsum(d1,l-1)-getsum(d2,l-1));
    }
    

    摘自


    咳咳,回归正题,总结一下
    普通差分就是这个数减去前一个数所得到的一个数组,他不是个算法,只是种技巧,比如在树状数组中的妙用,让树状数组具有区间查询,单点修改的功能。

    差分套差分(二阶差分)

    没错你没有听错,差分都可以套了!

    好像又叫二阶差分。

    怎么套?将差分数组再差分一遍,求到了差分套差分的数组,定位c数组。

    那么,推一推,发现(a[i]=c[i]*1+c[i-1]*2+...+c[1]*(i-1))

    嗯,这个有什么用呢?

    如果有一个毒瘤出题人,出了一道题(就是我被坑了,就写出来了):

    给你一个长度为n的序列a,有m次操作,每次操作让区间[l,r]分别加上t,t*2,t*3,...,t*(r-l+1)
    最后输出a序列的每个数的值
    

    把1操作中加上的数差分,就为(t,t,t,t,t,...,t)(注意:以后求高阶差分的修改公式,将加上的数组也进行差分来推是最好的!),那么,就等于给a的差分数组b区间加上t,那么就将b再差分出另一个差分数组c来更改,最后O(n)输出一下答案就好了。

    当然,相比差分,差分套差分会有更多应用,欢迎大家探究!

    高阶差分

    (n(n>1))阶等差数列就是两项之差的序列是(n-1)阶等差数列的序列。

    当然,要处理这个的话一般是用FFT来搞。

    想学习这个请食用FFT或者其他类似算法。

    树上等差数列

    基本概念:

    1. 树上两点之间只有一条最短路径
    2. 树上两点只有一个最近公共祖先

    1. 点差分

    点差分求什么?

    给你一棵树,并给你一些在树上的路径,让你求每个点在树上被经过的次数。

    整篇博客没一张图。。。
    在这里插入图片描述
    图中红色绿色蓝色代表三条路径,点旁边的标记代表他被经过的次数。

    如何求?
    暴力!
    在这里插入图片描述

    。。。
    DFS暴力的话,肯定过不了呀!如果你送毒瘤出题人足够刀片说不定可以。。。

    这时候,就有人跳出来发明了个算法,叫树上差分:
    设路径的开头与结尾为(st)(ed),设(k=lca(st,ed))(father_{k})(k)(father)
    那么我们把(f[st]++,f[ed]++,f[k]--,f[father_{k}]--),有什么用?
    在这里插入图片描述

    我们跑用DFS遍历一遍一棵树,设(tot_{i})(i)的子树的所有节点的(f)和,如图:在(st->ed)这条路径中,除了(ed)外,(tot)值都是(1),又因为(k)点的(f)值为(-1),所以将一个1消掉了,所以(k)的tot值也为1.

    某银:那(k)的父亲呢?

    因为我们让(father_{k})(f值也减了1),所以他和(k)的1抵消了,所以并没有影响。

    所以我们只需要(O(1))将所有路径处理完,(O(n))遍历处理答案就好了!

    2. 边差分

    跟点差分差不多。

    给你一棵树,并给你一些在树上的路径,让你求每条边在树上被经过的次数。

    在这里插入图片描述

    首先,我们应当考虑把边压到点里面,那么我们就让每个点到父亲的那条边压到这个点身上,然后求每个点被经过的次数就行了,点差分一下,有什么难?

    恭喜你WA了。
    在这里插入图片描述

    难道你就没有发现只算一条路径的话,(k)到父亲这条边没有被经过,但是在点差分过程中(tot_{k}=1)吗?所以,我们应当改一下修改(f)值的过程。

    (f[st]++,f[ed]++,f[k]-=2)

    那么在(k)点的时候,就把(st、ed)的影响消掉了,是不是很舒服?

    最后DFS一遍,别忘了每个点代表的是他到父亲的边!

    那么不就解决了?

    至此,基础的差分结束了。

    终于写完了,ヾ(≧▽≦*)o,<( ̄ˇ ̄)/,~( ̄▽ ̄~)(~ ̄▽ ̄)~,(:逃

    欢迎大家D我,让我能更好的完善博客!

  • 相关阅读:
    char 型变量中能不能存贮一个中文汉字,为什么?
    抽象类(abstract class)和接口(interface)有什么异同?
    描述一下JVM加载class文件的原理机制?
    重载(Overload)和重写(Override)的区别。重载的方法能否根据返回类型进行区分?
    String和StringBuilder、StringBuffer的区别?
    此方法可改变这个对象的属性,并可返回变化后的结果,那么这里到底是值传递还是引用传递?
    是否可以继承String类?
    两个对象值相同(x.equals(y) == true),但却可有不同的hash code,这句话对不对?
    laraval join 的理解
    whereHasIn方法
  • 原文地址:https://www.cnblogs.com/zhangjianjunab/p/9914055.html
Copyright © 2011-2022 走看看