题目大意

你需要实现一种数据结构,支援以下操作。

  1. 给一个矩阵的子矩阵的所有元素同时加一个数。
  2. 计算子矩阵和。

题解

一看这个题,我就首先想到用线段树套线段树做。

使用二维线段树的错误解法

其实是第一次写二维线段树orz。为了方便,我们不再使用k<<1作为左儿子,k<<1|1作为右儿子,而是使用一个lc,rc数组来存储左孩子和右孩子。实现起来不是很麻烦但是有许多细节。我们可以在操作函数中写一个参数表示是操作的行还是操作的列。另外注意打标记。对于节点k,标记一旦下传,他的孩子的各种标记即被修改,而它自身的标记应该在更前面的一个时刻修改。同时注意update,因为这个WA了好多次。尽管这样,还是有许多错。小数据基本可以过,但大数据就不行了Orz,求各位大佬帮助修改。

使用二维树状数组的正解

首先我们先要把树状数组(单点修改)扩展的二维。很显然,我们可以直接在两个维度重现一维的操作,非常简单。

那么,对于区间修改,我们也要扩展到二维,我们回忆一维时候的做法,我们首先分类讨论,比较了修改一个区间对于一个前缀的影响,具体可以见《挑战程序设计竞赛》或者我的模板汇总贴。

我们继续这样考虑。记\(g(i,j)\)为修改后的二维前缀和,\(s(i,j)\)为修改前的二维前缀和。我们分类讨论,容易得出以下结论。

\[g(i,j) = s(i,j)
\]

\[g(i,j) = s(i,j) + (ij+(x_0-1)(y_0-1))k - (j(x_0-1)+i(y_0-1))k
\]

\[g(i,j) = s(i,j) + (iy_1+(x_0-1)(y_0-1))k - ((x_0-1)y_1+i(y_0-1))k
\]

\[g(i,j) = s(i,j) + (jx_1+(x_0-1)(y_0-1))k - (j(x_0-1)+x_1(y_0-1))k
\]

\[g(i,j) = s(i,j) + (x_1y_1+(x_0-1)(y_0-1))k - ((x_0-1)y_1+x_1(y_0-1))k
\]

上面的五个式子分别对应ij均小,ij均ok,i好j大,i大j好,i大j大五种情况。//上面的狮子\(x_0y_0\)均-1的原因是本蒟蒻开始推的时候忘记把区间前界-1了Orz

推演到这里就非常方便了。我们无论是使用差分的思想,还是使用乱搞的思想都非常好搞了。

具体地,我们开四个树状数组,分别表示与i,j均无关,与ij一个有关,与ij均有关四种情况,分清楚每一种情况的作用域,准确地运用差分即可。

其实,程序实现的时候有很多细节,一个是因为推导时候的错误,一个是因为树状数组从1开始存储,所以要把每个坐标都加一。

这个题写了一晚上,但AC了还是非常有成就感QAQ

代码(使用二维线段树的错误解答)

#include <algorithm>
#include <cstdio>
const int maxm = 3000;
const int maxn = 2048 * 2048 * 4;
int sum[maxn], ll[maxn], rr[maxn], add[maxn], lc[maxn], rc[maxn], sz = 0,
gg[maxm];
char wtf;
int n, m;
void pushdown(int k) {
if (add[k]) {
add[lc[k]] += add[k];
add[rc[k]] += add[k];
int mid = (ll[k] + rr[k]) >> 1;
sum[lc[k]] += (mid - ll[k] + 1) * (add[k]);
sum[rc[k]] += (rr[k] - mid) * (add[k]);
add[k] = 0;
}
return;
}
void update(int k) { sum[k] = sum[lc[k]] + sum[rc[k]]; }
int build(int k, int l, int r, int a) {
ll[k] = l, rr[k] = r;
if (l == r) {
if (a == 0) {
gg[k] = ++sz;
build(gg[k], 1, m, 1);
} else if (a == 1) {
sum[k] = 0;
}
return k;
}
int mid = (l + r) >> 1, left = build(++sz, l, mid, a),
right = build(++sz, mid + 1, r, a);
lc[k] = left, rc[k] = right;
sum[k] = sum[lc[k]] + sum[rc[k]];
return k;
}
void change(int k, int y0, int y1, int val) {
int l = ll[k], r = rr[k], mid = (l + r) >> 1;
if (y0 <= l && r <= y1) {
sum[k] += (r - l + 1) * (val);
add[k] = val;
return;
}
pushdown(k);
if (y0 <= mid)
change(lc[k], y0, y1, val);
if (y1 > mid)
change(rc[k], y0, y1, val);
update(k);
}
void plus(int k, int x0, int x1, int y0, int y1, int val) {
int l = ll[k], r = rr[k], mid = (l + r) >> 1;
if (x0 <= l && r <= x1 && l == r) {
change(gg[k], y0, y1, val);
return;
}
if (x0 <= mid)
plus(lc[k], x0, x1, y0, y1, val);
if (x1 > mid)
plus(rc[k], x0, x1, y0, y1, val);
update(k);
return;
}
int query(int k, int x0, int x1, int y0, int y1, int a) {
int l = ll[k], r = rr[k], mid = (l + r) >> 1;
if (a == 0) {
if (x0 <= l && r <= x1 && l == r) {
return query(gg[k], x0, x1, y0, y1, 1);
}
int ans = 0;
if (x0 <= mid)
ans += query(lc[k], x0, x1, y0, y1, 0);
if (x1 > mid)
ans += query(rc[k], x0, x1, y0, y1, 0);
return ans;
}
if (a == 1) {
if (y0 <= l && r <= y1) {
return sum[k];
}
pushdown(k);
int ans = 0;
if (y0 <= mid)
ans += query(lc[k], x0, x1, y0, y1, 1);
if (y1 > mid)
ans += query(rc[k], x0, x1, y0, y1, 1);
update(k);
return ans;
}
}
int main() {
#ifdef D
freopen("input", "r", stdin);
#endif
scanf("%c %d %d", &wtf, &n, &m);
int t = build(++sz, 1, n, 0);
char ch;
while (scanf("%c", &ch) == 1) {
if (ch == 'L') {
int x0, y0, x1, y1, val;
scanf("%d %d %d %d %d", &x0, &y0, &x1, &y1, &val);
if (x0 > x1) {
std::swap(x0, x1);
std::swap(y0, y1);
}
plus(t, x0, x1, y0, y1, val);
}
if (ch == 'k') {
int x0, y0, x1, y1;
scanf("%d %d %d %d", &x0, &y0, &x1, &y1);
if (x0 > x1 && y0 < y1) {
std::swap(y0, y1);
}
if (x0 > x1)
std::swap(x0, x1);
int ans = query(t, x0, x1, y0, y1, 0);
printf("%d\n", ans);
}
}
return 0;
}

代码(使用树状数组的正解)

#include <algorithm>
#include <cstdio>
#include <cstring>
#include <iostream>
#ifdef D
const int maxn = 30;
#else
const int maxn = 2052;
#endif
int n, m;
int c
[4][maxn]
[maxn]; // a:与i无关与j无关,b:与i有关与j无关,c:与i无关与j有关,d:与i有关与j有关
char opt;
void change(int id, int x, int y, int val) {
for (int i = x; i <= n; i += i & -i) {
for (int j = y; j <= m; j += j & -j) {
c[id][i][j] += val;
}
}
}
int qu(int id, int x, int y) {
int ans = 0;
for (int i = x; i > 0; i -= i & -i) {
for (int j = y; j > 0; j -= j & -j) {
ans += c[id][i][j];
}
}
return ans;
}
void pls(int x0, int y0, int x1, int y1, int k) {
change(0, x0 + 1, y0 + 1, (x0 * y0) * k);
change(0, x0 + 1, y1 + 1, -(x0 * y1) * k);
change(0, x1 + 1, y0 + 1, -(x1 * y0) * k);
change(0, x1 + 1, y1 + 1, (x1 * y1) * k);
//-------
change(1, x0 + 1, y0 + 1, -(y0 * k));
change(1, x1 + 1, y0 + 1, (y0 * k));
change(1, x0 + 1, y1 + 1, (y1 * k));
change(1, x1 + 1, y1 + 1, -(y1 * k));
//-------
change(2, x0 + 1, y0 + 1, -(x0 * k));
change(2, x0 + 1, y1 + 1, (x0 * k));
change(2, x1 + 1, y0 + 1, (x1 * k));
change(2, x1 + 1, y1 + 1, -(x1 * k));
//-------
change(3, x0 + 1, y0 + 1, k);
change(3, x0 + 1, y1 + 1, -k);
change(3, x1 + 1, y0 + 1, -k);
change(3, x1 + 1, y1 + 1, k);
}
int sum(int x, int y) {
return qu(0, x, y) + (qu(1, x, y) * x) + (qu(2, x, y) * y) +
(qu(3, x, y) * x * y);
}
int query(int x0, int y0, int x1, int y1) {
int sum1 = sum(x0 - 1, y0 - 1);
int sum2 = sum(x0 - 1, y1);
int sum3 = sum(x1, y0 - 1);
int sum4 = sum(x1, y1);
return sum4 - sum2 - sum3 + sum1;
}
int main() {
#ifdef D
freopen("input", "r", stdin);
#endif
scanf("%c %d %d", &opt, &n, &m);
n++, m++;
memset(c, 0, sizeof(c));
while (scanf("%c", &opt) == 1)
if (opt == 'L' || opt == 'k') {
int x0, y0, x1, y1;
scanf("%d %d %d %d", &x0, &y0, &x1, &y1);
x0++;
x1++;
y0++;
y1++;
if (x0 > x1) {
std::swap(x0, x1);
std::swap(y0, y1);
}
if (opt == 'L') {
int v;
scanf("%d ", &v);
x0--, y0--;
pls(x0, y0, x1, y1, v);
}
if (opt == 'k') {
#ifdef D
for (int k = 0; k < 4; k++) {
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++)
std::cout << c[k][i][j] << ' ';
std::cout << std::endl;
}
std::cout << std::endl << std::endl;
}
#endif
int ans = query(x0, y0, x1, y1);
printf("%d\n", ans);
}
}
}

[bzoj3132]上帝造题的七分钟——二维树状数组的更多相关文章

  1. 【bzoj3132】上帝造题的七分钟 二维树状数组区间修改区间查询

    题目描述 “第一分钟,X说,要有矩阵,于是便有了一个里面写满了0的n×m矩阵. 第二分钟,L说,要能修改,于是便有了将左上角为(a,b),右下角为(c,d)的一个矩形区域内的全部数字加上一个值的操作. ...

  2. 【BZOJ3132】【TYVJ1716】上帝造题的七分钟 二维树状数组

    题目大意 维护一个\(n\times m\)的矩阵,有两种操作: \(1~x_1~y_1~x_2~y_2~v\):把\((a,b),(c,d)\)为顶点的矩形区域内的所有数字加上\(v\). \(2~ ...

  3. tyvj P1716 - 上帝造题的七分钟 二维树状数组区间查询及修改 二维线段树

    P1716 - 上帝造题的七分钟 From Riatre    Normal (OI)总时限:50s    内存限制:128MB    代码长度限制:64KB 背景 Background 裸体就意味着 ...

  4. P4514 上帝造题的七分钟——二维树状数组

    P4514 上帝造题的七分钟 求的是矩阵里所有数的和: 维护四个树状数组: #include<cstdio> #include<cstring> #include<alg ...

  5. BZOJ 3132: 上帝造题的七分钟( 二维BIT )

    二维树状数组... 自己YY一下再推一下应该可以搞出来... --------------------------------------------------------------------- ...

  6. POJ 2029 Get Many Persimmon Trees (模板题)【二维树状数组】

    <题目链接> 题目大意: 给你一个H*W的矩阵,再告诉你有n个坐标有点,问你一个w*h的小矩阵最多能够包括多少个点. 解题分析:二维树状数组模板题. #include <cstdio ...

  7. P4514 上帝造题的七分钟(二维树状数组)

    P4514 上帝造题的七分钟 二维树状数组 差分维护区间加法,区间求和 #include<cstdio> int read(){ ,f=; ') f=f&&(c!='-') ...

  8. POJ 2155 Matrix (二维树状数组)

    Matrix Time Limit: 3000MS   Memory Limit: 65536K Total Submissions: 17224   Accepted: 6460 Descripti ...

  9. POJ_2155 Matrix 【二维树状数组】

    一.题面 POJ2155 二.分析 楼教主出的题,是二维树状数组非常好的题,还结合了开关问题(开关变化的次数如果为偶数,状态不变,奇数状态相反). 题意就是给了一个二维的坐标平面,每个点初始值都是0, ...

随机推荐

  1. 基于Mysql-Proxy实现Mysql的主从复制以及读写分离(上)

    基于Mysql-Proxy实现Mysql的主从复制以及读写分离(上) 上周BOSS给分配任务让实现一下Mysql数据库的主从复制以及读写分离,然后花了一盏茶的功夫进行了调研,发现主从复制数据库进行一番 ...

  2. 《python核心编程第二版》第4章习题

    4–1. Python 对象.与所有 Python 对象有关的三个属性是什么?请简单的

  3. Unity和Lua交互

    用lua就表示项目用到了热更新,通常每次热更新都会从服务器获取最新的lua脚本放到Android/ios设备的本地目录下,但是lua应该放到哪个目录下呢,这里就先说说lua里面的路径问题 1.不可以放 ...

  4. java设计模式之装饰器模式以及在java中作用

    在JAVA I/O类库里有很多不同的功能组合情况,这些不同的功能组合都是使用装饰器模式实现的,下面以FilterInputStream为例介绍装饰器模式的使用  FilterInputStream和F ...

  5. Struts2—整合Spring

    Struts2—整合Spring Spring框架是一个非常优秀的轻量级java EE容器,大部分javaEE应用,都会考虑使用Spring容器来管理应用中的组件. Struts2是一个MVC框架,是 ...

  6. C#编译和运行原理

    关于编译与内存的关系,以及执行时内存的划分 1.所谓在编译期间分配空间指的是静态分配空间(相对于用new动态申请空间),如全局变量或静态变量(包括一些复杂类型的 常量),它们所需要的空间大小可以明确计 ...

  7. valgrind使用

    参数配置 gcc -g: 增加调试信息,供valgrind精确定位. -O0:关闭gcc优化:优化产生的代码可能会造成valgrind误判. valgrind --leak-check=full no ...

  8. vue2.0中父子组件之间的通信总结

    父组件: 子组件: 接受父组件的信息: 向父组件发送事件: (其中slot是插槽,可以将父组件中的<p>123</p>插入进来,如果父组件没有插入的内容,则显示slot内部的内 ...

  9. [C/C++] C++中new的语法规则

    int *x = new int; //开辟一个存放整数的存储空间,返回一个指向该存储空间的地址(即指针) ); //开辟一个存放整数的空间,并指定该整数的初值为100,返回一个指向该存储空间的地址 ...

  10. 习题:就是干(DP)

    洛谷2301 题目描述 眼看着老师大军浩浩荡荡的向机房前进.LOI 的同学们决定动用自己的力量来保卫他们的好朋友loidc.现在每个人都要挑选自己的武器——两根木棍.一根用做远距离投掷,另一根用做近距 ...