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;
    }
    
  • 相关阅读:
    leetcode Remove Linked List Elements
    leetcode Word Pattern
    leetcode Isomorphic Strings
    leetcode Valid Parentheses
    leetcode Remove Nth Node From End of List
    leetcode Contains Duplicate II
    leetcode Rectangle Area
    leetcode Length of Last Word
    leetcode Valid Sudoku
    leetcode Reverse Bits
  • 原文地址:https://www.cnblogs.com/fmj123/p/Luogu4514.html
Copyright © 2011-2022 走看看