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) 为顶点的矩形区域内所有数字的和。
这两种操作
我们从一维树状数组的“区间修改,区间查询“开始
使用差分的方法,差分数组\(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
- shell中echo的显示格式 (r5笔记第58天)
- springboot 入门教程(5) 基于ssm框架的crud操作(前端部分-附源码)
- springboot入门(4)_web开发
- springboot入门教程(2)_Thymeleaf集成
- VList data structures in C#
- 编程思想 之「语言导论」
- 编程思想 之「对象漫谈」
- Github 项目推荐 | TensorFlow 概率推理工具集 —— probability
- Github 项目推荐 | 用于 C/C++、Java、Matlab/Octave 的特征选择工具箱
- Mercari Price 比赛分享 —— 语言不仅是算法和公式而已
- Github 项目推荐 | GAN 的 Keras 实现案例集合 —— Keras-GAN
- Github 项目推荐 | 微软开源 MMdnn,模型可在多框架间转换
- 半自动化运维之动态添加数据文件(一) (r5笔记第55天)
- 半自动化运维之动态添加数据文件(二) (r5笔记第56天)
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Pycharm中安装Pygal并使用Pygal模拟掷骰子(推荐)
- 解决python多线程报错:AttributeError: Can't pickle local object问题
- TensorFlow2.1.0最新版本安装详细教程
- Android仿京东首页秒杀倒计时
- 超实用的android自定义log日志输出工具类
- spring进行mock测试
- RecyclerView实现纵向和横向滚动
- Android ListView列表视图的使用方法
- 滴滴自动化运维平台夜莺实战部署
- Android UI使用HorizontalListView实现水平滑动
- python操作yaml说明
- python由已知数组快速生成新数组的方法
- Git 修改已提交 commit 的信息
- 解决windows下python3使用multiprocessing.Pool出现的问题
- 解决pyqt5异常退出无提示信息的问题