zoukankan      html  css  js  c++  java
  • Luogu P4514 上帝造题的七分钟|二维树状数组

    //两年前的blog

    • 题目大意:有一个写满0的矩阵,要求有
      1. L a b c d delta 代表将 (a,b),(c,d)(a,b),(c,d) 为顶点的矩形区域内的所有数字加上delta。
    
      2. k a b c d 代表求 (a,b),(c,d)(a,b),(c,d) 为顶点的矩形区域内所有数字的和。
    

    这两种操作

    • 总结:区间修改,区间查询

      OK ,看到这dalao们的反应都是线段树或树状数组了,因为听说线段树过不去,所以我用了树状数组。


    树状数组怎么用?如何区间修改,单点查询?请参考

    3374

    3368


    我们从一维树状数组的“区间修改,区间查询“开始

    使用差分的方法,差分数组(d_i)为第(i)个数((a_i))与第(i-1)((a_{i-1}))个数的差,那么

    因为 (a_i=d_1+d_2+...+d_{i-1}+d_i)

    所以 (d_i)实际上用了(n-(i-1))次,相当于有(i-1)次没被使用

    所以 我们可以建两个树状数组,

    第一个(本部分称为(tree))存(d_i)

    第二个(本部分称为(tree1))存(d_i*(i-1))

    求1~x的总和就是 (tree)中x的前缀和*x-(tree1)中x的前缀和

    对于此公式,我的思路是先把x前面的数看成(a_x),再减去多算的值((tree1)数组)

    贴代码(P3372)

    // luogu-judger-enable-o2
    //本代码中,tree和tree1的意义与前文相同
    #include<bits/stdc++.h>
    using namespace std;
    long long n,tree[101000],tree1[100000],m,c,l,r,k;
    void add(int x,int y)
    {
        //修改tree
        for (int i=x;i<=n;i+=(i&(-i)))
            tree[i]+=y;
    }
    void add1(int x,int y)
    {
        //修改tree1
        for (int i=x;i<=n;i+=(i&(-i)))
            tree1[i]+=y;
    }
    long long ask(long long x)
    {
        long long ans=0,ans1=0;
        for (long long i=x;i;i-=(i&(-i)))
          ans+=tree[i];
        ans*=x;
        for (long long i=x;i;i-=(i&(-i)))
          ans1+=tree1[i];
        //分别求出ans*x与ans1(应该可以合起来写)
        return ans-ans1;
    }
    int main()
    {
        scanf("%lld%lld",&n,&m);
        long long c=0,last=0,a=0;
        for (long long i=1;i<=n;i++)
        {
            scanf("%lld",&a);//long long读入要用lld
            c=a-last;//求差分
            add(i,c);add1(i,c*(i-1));
            last=a;
        }
        for (long long i=1;i<=m;i++)
        {
            scanf("%lld",&c);
            if (c==1)
            {
              //区间修改
              scanf("%lld%lld%lld",&l,&r,&k);
                add(l,k);add(r+1,-k);//在l处+x
                add1(l,k*(l-1));add1(r+1,(-k)*(r));//在r+1处-x,相当于抵消前面的+x
            }
            if (c==2)
            {
               //区间查询 
               scanf("%d%d",&l,&r);
                cout<<ask(r)-ask(l-1)<<endl;
            }
        }
        return 0;
    }
    

    回归正题,二维树状数组的区间修改,区间查询怎么做呢?

    (二维树状数组的定义可以baidu一下)

    因为差分实际上是前缀和的逆运算,所以,仿照二维前缀和((s[i][j]=a[i][j]+a[i-1][j]+a[i][j-1]-a[i-1][j-1])),我们可以写出二维差分公式
    (d[i][j]=a[i][j]-a[i-1][j]-a[i][j-1]+a[i-1][j-1])

    修改就是这样

    //原数组
    0 x x x 
    0 x x x
    0 x x x
    
    //差分数组
    0 +x  0 0 -x
    0 0   0 0 0
    0 0   0 0 0 
    0 -x  0 0 +x
    

    仿照一维的“区间修改,区间查询”我们可以得出

    (d[i][j])实际上被使用了((n-i+1)(m-j+1))次,相当于有(j(i-1)+i(j-1)+(i-1)(j-1))次没被使用

    因此,我们可以维护4个树状数组

    第一个(本部分称为(tree))存(d[i][j])

    第二个(本部分称为(tree1))存(d[i][j]*(i-1))

    第三个(本部分称为(tree2))存(d[i][j]*(j-1))

    第二个(本部分称为(tree3))存(d[i][j]*(i-1)*(j-1))

    最后,求((1,1))((i,j))的前缀和就是

    (tree[i][j])的前缀和(*x*y)-(tree1[i][j])的前缀和(*j)-(tree2[i][j])的前缀和(*i)+(tree3[i][j])的前缀和

    (这个公式和普通的前缀和差不多啊好像)

    我的思路和前面一样,先把所有数看成(a[i][j]),再减差值

    最后贴本题代码

    // luogu-judger-enable-o2
    //本代码中,tree、tree1、tree2、tree3的意义与前文相同
    //码风不好求轻喷
    #include<bits/stdc++.h>
    using namespace std;
    long long tree[2049][2049],tree1[2049][2049],tree2[2049][2049],tree3[2049][2049];
    long long n,m,a,b,c,d,ad;char ch;
    void add(long long x,long long y,long long z)
    {
        for (long long i=x;i<=n;i+=i&(-i))
        {
            for (long long j=y;j<=m;j+=(j&(-j)))
            {
                tree[i][j]+=z;
                tree1[i][j]+=z*(x-1);
                tree2[i][j]+=z*(y-1);
                tree3[i][j]+=z*(x-1)*(y-1);//分别修改四个数组
            }
        }
    }
    void fa(long long a,long long b,long long c,long long d,long long ad)
    {
        //分别修改四个点(原谅这个哲学函数)
        add(a,b,ad);add(c+1,b,-ad);
        add(a,d+1,-ad);add(c+1,d+1,ad);
    }
    long long ask(long long x,long long y)
    {
        long long ans=0,ans1=0,ans2=0,ans3=0;
        for (long long i=x;i;i-=(i&(-i)))
        {
            for (long long j=y;j;j-=(j&(-j)))
            {
                ans+=tree[i][j];
                ans1+=tree1[i][j];
                ans2+=tree2[i][j];
                ans3+=tree3[i][j];
            }
        }
        ans*=x*y;	
        //分别求和
        return ans-ans1*y-ans2*x+ans3;
    }
    int main()
    {
        cin>>ch;
        scanf("%d%lld",&n,&m);
        while (cin>>ch)
        {
            if (ch=='L')
            {
             //修改
             scanf("%lld%lld%lld%lld%lld",&a,&b,&c,&d,&ad);
              fa(a,b,c,d,ad);
            }
            if (ch=='k')
            {
            //查询
            	scanf("%lld%lld%lld%lld",&a,&b,&c,&d);
            	cout<<ask(c,d)-ask(c,b-1)-ask(a-1,d)+ask(a-1,b-1)<<endl;
            }
        }
        return 0;
    }
    
  • 相关阅读:
    scrapy相关信息
    BeautifulSoup常见使用
    requests常用模块以及爬取github个人项目
    django rest framework 与前端跨域问题解决
    nginx配置正向代理与反向代理
    django+nginx+uwsgi+https
    linux基本命令
    python基本算法
    centos7配置ftp服务器
    nginx1.12配置
  • 原文地址:https://www.cnblogs.com/fmj123/p/Luogu4514.html
Copyright © 2011-2022 走看看