Luogu P4514 上帝造题的七分钟|二维树状数组

时间:2019-11-14
本文章向大家介绍Luogu P4514 上帝造题的七分钟|二维树状数组,主要包括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;
}

原文地址:https://www.cnblogs.com/fmj123/p/Luogu4514.html