差分 详解

定义

差分这个名词实在难理解。。也不知道是哪个大佬取的名儿。。。差分是一种思想,我们拿一个例题来说吧:

糖果
题目描述
现在有n(1 <= N <= 1,000,000, N 是奇数)个盒子,编号是1..n。
数学老师为了惩罚他,决定让他做一个难题,他让小x会对这些盒子做k(1 <=k <= 25,000)次放糖块的操作(这得多少糖块呀)。
数学老师每次会给小x一个区间[a,b],这时小x就会从编号是a的盒子到编号是b的盒子每个盒子都放一个糖块。
做完k次操作后,数学老师会问小x,在所有盒子的糖块个数中,这些糖块个数的中位数是多少。(最中间的值)。
因为n是奇数,所以这个答案肯定是唯一的。
输入格式
第一行:两个整数 n k。 接下来k行,每行一个区间 ai bi ,表示要放糖块的区间。
输出格式
一个整数,表示中位数的值。
样例数据
input
7 4
5 5
2 4
4 6
3 5
output
1
【样例解释】一共有7个盒子,4个操作,第一次在5号盒子里放1个,第二次在2 3 4号盒子里放。etc
放完糖块之后,盒子里的糖块依次是0,1,2,3,3,1,0.排过序后,数字1是中位数。
数据规模与约定
usaco 2011 stacking
时间限制:
1s
1s
空间限制:
256MB

分析:我们这道题目用的是差分的思想。怎么实现呢,我们把线段拆成两条射线的差。比如我们需要给一个区间 [x,y] 进行操作,我们只要把x及x以后的所有元素+1,再把y+1及y+1以后的所有元素-1,最后的结果就是x到y这个区间+1了。具体实现方式和线段树的lazy标记有点相似。比如我们要给 [2,6] 这个区间+1,那就在2的位置打上+1标记,表示2以后的所有元素都+1;再在7的位置打上-1标记,表示7以后的所有元素都-1.这样到底有什么用呢?我们画一张表格看一看:
我们做好标记之后,这个数组是这样的:

010000-10

然后求一下子他的前缀和:

01111100

所以发现这个前缀和数组中所有为1的元素就是我们需要操作的区间。我们实现了 O(1) 时间的修改和 O(n) 时间的完善( pushdown )。
code:

#include<bits/stdc++.h>
#define maxn 1000100
using namespace std;
inline int read()
{
    int num=0;
    bool flag=true;
    char c;
    for(;c>'9'||c<'0';c=getchar())
    if(c=='-')
    flag=false;
    for(;c>='0'&&c<='9';num=num*10+c-48,c=getchar());
    return flag ? num : -num;
}
int n,k,a[maxn],delta[maxn];
int main()
{
    n=read();
    k=read();
    for(int i=1;i<=k;i++)
    {
        int x=read();
        int y=read();
        delta[x]++;
        delta[y+1]--;
        //把偏移量记录下来。
    }
    for(int i=1;i<=n;i++)
    a[i]=a[i-1]+delta[i];
    //做前缀和
    sort(a+1,a+1+n);
    //排序,方便求中位数
    printf("%d",a[n/2+1]);
    return 0; 
}

例题

[USACO NOV]金发姑娘和N头牛
★☆ 输入文件:milktemp.in 输出文件:milktemp.out 简单对比
时间限制:1 s 内存限制:256 MB
【题目描述】
你可能已经听说了金发姑娘和3只熊的经典故事。
鲜为人知的是,金发姑娘最终经营了一个农场。在她的农场,她有一个谷仓含N头奶牛 1N20000) 。不幸的是,她的奶牛对温度相当敏感。
第i头奶牛必须在指定的温度范围内A(i)..B(i)才感觉舒适; 0A(i)B(i)1,000,000,000 。如果金发姑娘在谷仓放置一个温控器;如果温度 T<A(i) ,牛会太冷,并将产生x单位牛奶。如果她把恒温器调到 A(i)TB(i) 这个范围内,那么牛会感到舒适,并将产生Y单位牛奶。如果她把恒温器调到温度T>B(i),牛会感觉很热,并将产生的Z单位牛奶。正如预期的那样,Y的值总是大于X和Z。
给定的X,Y,和Z,以及每个牛的温度的最佳范围,如果金发姑娘设置谷仓的温控器最佳,请计算金发姑娘得到牛奶的最大数量,已知X,Y和Z都是整数,范围0..1000。温控器可以设置为任意整数的值。
【输入格式】
第1行:四个用空格隔开的整数:N X Y Z。
第2行..1 + N:行1+i包含两个用空格隔开的整数:A(i)和B(i)。
【输出格式】
1行:金发姑娘最多可以获得的牛奶,当她在谷仓的最佳温度设定。
【样例输入】
4 7 9 6
5 8
3 4
13 20
7 10
【样例输出】
31
【提示】
在农场里有4头奶牛,温度范围5..8,3..4,13..20,10..7。一个寒冷的奶牛生产7单位的牛奶,一个舒适的奶牛生产9个单位的牛奶,一个热牛生产6单位牛奶。
【数据规模】
50%的测试数据: n5
其余50%的测试数据: 10000<n20000.
【来源】
USACO 2013 November Contest, Bronze

分析

这道题目无非就是要对温度区间干一些猥琐的事情。我们不能接受 O(n2) 的算法,所以我们只能选择差分的思想。对于一段区间 [1,A(i)1] ,我们需要加上x的收益,对于一段区间 [A(i),B(i)] ,我们需要加上y的收益,对于一段区间 [B(i)1,n] ,我们需要加上z的收益,所以说,我们只要在 1 的位置上加上x,在A(i)的位置上加上y-x,在 B(i)+1 的位置上加上z-y,那么就实现了所有元素的修改。最后前缀和就是奶牛在这个温度的产生的牛奶量。
但是我们很无奈。 A(i)B(i) 的范围真的大!别说O(n+m)了,就连O(n)都超时。
这里写图片描述
我们有一个利器:离散化。由于我们只关心A数组和B数组元素的大小关系,并不关心实际数值,我们可以把两个坐标数组排序一下子,然后通过map映射成下标,映射到一个短小的离散化数组中。再进行区间修改就很好了。

code

#include<bits/stdc++.h>
#define maxn 21000
using namespace std;
inline int read()
{
    int num=0;
    bool flag=true;
    char c;
    for(;c>'9'||c<'0';c=getchar())
    if(c=='-')
    flag=false;
    for(;c>='0'&&c<='9';num=num*10+c-48,c=getchar());
    return flag ? num : -num;
}
int n,x,y,z,l[maxn],r[maxn];
int main()
{
    n=read();
    x=read();
    y=read();
    z=read();
    for(int i=1;i<=n;i++)
    {
        l[i]=read();
        r[i]=read()+1;
    }//这里记录的是需要标记的点,所以r数组要+1。
    sort(l+1,l+1+n);
    sort(r+1,r+1+n);
    //排序坐标
    map<int,int>m;
    //开辟一个从int映射到int的map
    int ans[maxn*2]={};
    int h=1,t=1;
    int cnt=0;
    while(true)
    {
        if(h<=n&&l[h]<r[t])
        {
            if(!m.count(l[h])) m[l[h]]=++cnt;
            ans[m[l[h]]]+=y-x;
            h++; 
        }
        else if(t<=n)
        {
            if(!m.count(r[t])) m[r[t]]=++cnt;
            ans[m[r[t]]]+=z-y;
            t++;
        }
        if(h>n && t>n)break;
    }
    int sum;
    int ans_=sum=n*x;
    for(int i=1;i<=cnt;i++)
    {
        sum+=ans[i];
        ans_=max(ans_,sum);
    }
    //一边做前缀和,一边找最大值
    printf("%d",ans_);
    return 0; 
}

树上差分

预备知识:lca
不会的同学可以看下这篇blog:用倍增求LCA
树上差分和线性差分的最基本思想是一模一样的。
点的差分
我们在线性差分上面是吧线段拆成射线,某一个点的标记代表它及以后的元素全部做一个变化;那我们在树上的某一个结点的标记就代表它所有的父亲(包括自己)都做一个变化。假如我们要在两个节点 (x,y) 之间的路径上的点全部+1,那么我们只需要在x上做+1的标记,y上做+1的标记, lca(x,y) 上做-1的标记, father[lca(x,y)] 上做-1的标记,就完成了xy之间路径的变化。最后从叶子结点开始做一个前缀和就可以了。
边的差分
因为边的差分和lca是没有关系的,所以我们只要在x和y上+1,lca上面-2就可以了,比点的差分更简单

例题

【bzoj 4390】 [Usaco2015 dec]Max Flow
题目描述
Farmer John has installed a new system of N-1 pipes to transport milk between the N stalls in his barn (2≤N≤50,0002 \leq N \leq 50,0002≤N≤50,000), conveniently numbered
1…N
1…N
. Each pipe connects a pair of stalls, and all stalls are connected to each-other via paths of pipes.
FJ is pumping milk between KKK pairs of stalls (1≤K≤100,0001 \leq K \leq 100,0001≤K≤100,000). For the iiith such pair, you are told two stalls sis_isi and tit_iti, endpoints of a path along which milk is being pumped at a unit rate. FJ is concerned that some stalls might end up overwhelmed with all the milk being pumped through them, since a stall can serve as a waypoint along many of the KKK paths along which milk is being pumped. Please help him determine the maximum amount of milk being pumped through any stall. If milk is being pumped along a path from sis_isi to tit_iti, then it counts as being pumped through the endpoint stalls sis_isi and tit_iti, as well as through every stall along the path between them.
FJ给他的牛棚的N(2≤N≤50,000)个隔间之间安装了N-1根管道,隔间编号从1到N。所有隔间都被管道连通了。
FJ有K(1≤K≤100,000)条运输牛奶的路线,第i条路线从隔间si运输到隔间ti。
一条运输路线会给它的两个端点处的隔间以及中间途径的所有隔间带来一个单位的运输压力,你需要计算压力最大的隔间的压力是多少。
输入格式
The first line of the input contains N and K.
The next N?1 lines each contain two integers x and y (x≠y) describing a pipe between stalls x and y.
The next K lines each contain two integers s and t describing the endpoint stalls of a path through which milk is being pumped.
输出格式
An integer specifying the maximum amount of milk pumped through any stall in the barn.
样例数据
input
5 10
3 4
1 5
4 2
5 4
5 4
5 4
3 5
4 3
4 3
1 3
3 5
5 4
1 5
3 4
output
9
数据规模与约定
时间限制:
1s
1s
空间限制:
256MB
256MB

分析

树上的变化!就用上述的办法。

code

#include<bits/stdc++.h>
#define maxn 50200
#define maxk 100200
#define INF 2000000000
using namespace std;
inline int read()
{
    int num=0;
    bool flag=true;
    char c;
    for(;c>'9'||c<'0';c=getchar())
    if(c=='-')
    flag=false;
    for(;c>='0'&&c<='9';num=num*10+c-48,c=getchar());
    return flag ? num : -num;
}

namespace graph//这是一个建树过程
{
    int n,k,head[maxn],top=0;
    struct Hy
    {
        int dot_order,next_location;
    }a[maxn*2];
    void insert(int x,int y)
    {
        top++;
        a[top].dot_order=y;
        a[top].next_location=head[x];
        head[x]=top;
    }
    void init()
    {
        n=read();
        k=read();
        for(int i=1;i<n;i++)
        {
            int x=read();
            int y=read();
            insert(x,y);
            insert(y,x);
        }
    }
}using namespace graph;

namespace beizeng_lca//用倍增求LCA
{
    int father[maxn][64],depth[maxn];
    void hy(int u)
    {
        for(int i=head[u];i;i=a[i].next_location)
        {
            int to=a[i].dot_order;
            if(to==father[u][0])continue;
            father[to][0]=u;
            depth[to]=depth[u]+1;
            hy(to);
        }
    }
    void BZ_yuchuli()
    {
        for(int j=1;(1<<j)<=n;j++)
            for(int i=1;i<=n;i++)
                father[i][j]=father[father[i][j-1]][j-1];
    }
    int LCA(int a,int b)
    {
        if(depth[a]>depth[b])swap(a,b);
        int f=depth[b]-depth[a];
        for(int i=0;(1<<i)<=f;i++)
        {
            if((1<<i)&f)b=father[b][i];
        }
        if(a!=b)
        {
            for(int i=(int)log2(n);i>=0;i--)
            {
                if(father[a][i]!=father[b][i])
                {
                    a=father[a][i];
                    b=father[b][i];
                }
            }
            a=father[a][0];
        }
        return a;
    }
}using namespace beizeng_lca;

namespace solution
{
    int ans[maxn],maxa;
    void dfs(int u)//深度优先遍历求“前缀和”
    {
        for(int i=head[u];i;i=a[i].next_location)
        {
            int to=a[i].dot_order;
            if(to==father[u][0])continue;
            dfs(to);
            ans[u]+=ans[to];//从叶子结点出发求前缀和
        }
        maxa=max(maxa,ans[u]);//找出最大值
    }
}using namespace solution;

int main()
{
    init();
    hy(1);
    BZ_yuchuli();
    for(int i=1;i<=k;i++)
    {
        int x=read();
        int y=read();
        int l=LCA(x,y);
        ans[x]++;
        ans[y]++;
        ans[l]--;
        if(l!=1)ans[father[l][0]]--;
        //x和y++,他们的lca和lca的父亲-1.
    }
    maxa=-INF;
    dfs(1);//遍历一下子
    printf("%d\n",maxa);
    return 0; 
}

GG

  • 16
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值